From f3ceeae61a834c3f0c75e2856a34f34074bdd781 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Thu, 15 Aug 2013 15:17:29 +0200 Subject: [PATCH 001/151] add a script to convert dragon 32 Cassetts WAV files into plain text. --- HelloWorld1 origin.wav | Bin 0 -> 225708 bytes HelloWorld1 xroar.wav | Bin 0 -> 82204 bytes dragon32_CAS_decode.py | 161 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 161 insertions(+) create mode 100755 HelloWorld1 origin.wav create mode 100755 HelloWorld1 xroar.wav create mode 100755 dragon32_CAS_decode.py diff --git a/HelloWorld1 origin.wav b/HelloWorld1 origin.wav new file mode 100755 index 0000000000000000000000000000000000000000..489c4468484a522c11465474025321cba354613b GIT binary patch literal 225708 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 z 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 a--v52G3AqfNL;t31-zoKs@<8&bN>Q=VNTTm literal 0 HcmV?d00001 diff --git a/dragon32_CAS_decode.py b/dragon32_CAS_decode.py new file mode 100755 index 00000000..409d4171 --- /dev/null +++ b/dragon32_CAS_decode.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python2 + +""" + Convert dragon 32 Cassetts WAV files into plain text. + ===================================================== + + In current state only the bits would be decoded, yet! + + TODO: + detect even_odd startpoint! + + Interesting links: + http://www.onastick.clara.net/cosio.htm + http://www.cs.unc.edu/~yakowenk/coco/text/tapeformat.html + + :copyleft: 2013 by Jens Diemer + :license: GNU GPL v3 or above, see LICENSE for more details. +""" + +import collections +import itertools +import wave +import sys +import struct +import audioop + + +ONE_HZ = 2400 # "1" is a single cycle at 2400 Hz +NUL_HZ = 1200 # "0" is a single cycle at 1200 Hz +MIN_TOGGLE_COUNT = 3 # How many samples must be in pos/neg to count a cycle? + + +def iter_wave_values(wavefile): + samplewidth = wavefile.getsampwidth() # i.e 1 for 8-bit samples, 2 for 16-bit samples + print "samplewidth:", samplewidth + nchannels = wavefile.getnchannels() # typically 1 for mono, 2 for stereo + print "channels:", nchannels + + assert nchannels == 1, "Only MONO files are supported, yet!" + + frame_count = wavefile.getnframes() # number of audio frames + + # FIXME + if samplewidth==1: + struct_unpack_str = "b" + elif samplewidth == 2: + struct_unpack_str = ">> count_sign([3,-1,-2]) + (1, 2) + >>> count_sign([0,-1]) + (0, 1) + """ + positive_count = 0 + negative_count = 0 + for value in values: + if value>0: + positive_count += 1 + elif value<0: + negative_count += 1 + return positive_count, negative_count + + +def iter_bits(wavefile, even_odd): + framerate = wavefile.getframerate() # frames / second + print "Framerate:", framerate + + in_positive = even_odd + in_negative = not even_odd + toggle_count = 0 # Counter for detect a complete cycle + previous_frame_no = 0 + + window_values = collections.deque(maxlen=MIN_TOGGLE_COUNT) + for frame_no, value in iter_wave_values(wavefile): + #~ ms=float(frame_no)/framerate + #~ print "%i %0.5fms %i" % (frame_no, ms, value) + + window_values.append(value) + if len(window_values)>=MIN_TOGGLE_COUNT: + positive_count, negative_count = count_sign(window_values) + + #~ print window_values, positive_count, negative_count + if not in_positive and positive_count==MIN_TOGGLE_COUNT and negative_count==0: + # go into a positive sinus area + in_positive = True + in_negative = False + toggle_count += 1 + elif not in_negative and negative_count==MIN_TOGGLE_COUNT and positive_count==0: + # go into a negative sinus area + in_negative = True + in_positive = False + toggle_count += 1 + + if toggle_count>=2: + # a single sinus cycle complete + toggle_count = 0 + + frame_count = frame_no-previous_frame_no + hz=framerate/frame_count + ms=float(frame_no)/framerate + + dst_one = abs(ONE_HZ-hz) + dst_nul = abs(NUL_HZ-hz) + if dst_one100: + #~ break + line += str(bit) + if len(line)>70: + print line + line = "" + + print "%i bits decoded." % bit_count From 283cf05d2a90b30f6b4c6500cbcd5dc8a41d7e20 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Thu, 15 Aug 2013 18:33:52 +0200 Subject: [PATCH 002/151] cut out the relevant data --- dragon32_CAS_decode.py | 295 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 265 insertions(+), 30 deletions(-) diff --git a/dragon32_CAS_decode.py b/dragon32_CAS_decode.py index 409d4171..d79fd675 100755 --- a/dragon32_CAS_decode.py +++ b/dragon32_CAS_decode.py @@ -3,9 +3,9 @@ """ Convert dragon 32 Cassetts WAV files into plain text. ===================================================== - + In current state only the bits would be decoded, yet! - + TODO: detect even_odd startpoint! @@ -29,6 +29,77 @@ NUL_HZ = 1200 # "0" is a single cycle at 1200 Hz MIN_TOGGLE_COUNT = 3 # How many samples must be in pos/neg to count a cycle? +DISPLAY_BLOCK_COUNT = 8 # How many bit block should be printet in one line? + + +def iter_steps(g, steps): + """ + >>> for v in iter_steps([1,2,3,4], steps=2): v + [1, 2] + [3, 4] + >>> for v in iter_steps([1,2,3,4,5,6], steps=3): v + [1, 2, 3] + [4, 5, 6] + + 12345678 12345678 + 12345678 + >>> bits = [int(i) for i in "0101010101010101111000"] + >>> for v in iter_steps(bits, steps=8): v + [0, 1, 0, 1, 0, 1, 0, 1] + [0, 1, 0, 1, 0, 1, 0, 1] + [1, 1, 1, 0, 0, 0] + """ + values = [] + for value in g: + values.append(value) + if len(values)==steps: + yield list(values) + values = [] + if values: + yield list(values) + + +def iter_window(g, steps): + """ + >>> for v in iter_window([1,2,3,4], steps=2): v + [1, 2] + [2, 3] + [3, 4] + >>> for v in iter_window([1,2,3,4,5], steps=3): v + [1, 2, 3] + [2, 3, 4] + [3, 4, 5] + + >>> for v in iter_window([1,2,3,4], steps=2): + ... v + ... v.append(True) + [1, 2] + [2, 3] + [3, 4] + """ + values = collections.deque(maxlen=steps) + for value in g: + values.append(value) + if len(values)==steps: + yield list(values) + + +def count_sign(values): + """ + >>> count_sign([3,-1,-2]) + (1, 2) + >>> count_sign([0,-1]) + (0, 1) + """ + positive_count = 0 + negative_count = 0 + for value in values: + if value>0: + positive_count += 1 + elif value<0: + negative_count += 1 + return positive_count, negative_count + def iter_wave_values(wavefile): samplewidth = wavefile.getsampwidth() # i.e 1 for 8-bit samples, 2 for 16-bit samples @@ -59,23 +130,6 @@ def iter_wave_values(wavefile): yield frame_no, frame -def count_sign(values): - """ - >>> count_sign([3,-1,-2]) - (1, 2) - >>> count_sign([0,-1]) - (0, 1) - """ - positive_count = 0 - negative_count = 0 - for value in values: - if value>0: - positive_count += 1 - elif value<0: - negative_count += 1 - return positive_count, negative_count - - def iter_bits(wavefile, even_odd): framerate = wavefile.getframerate() # frames / second print "Framerate:", framerate @@ -125,22 +179,141 @@ def iter_bits(wavefile, even_odd): previous_frame_no = frame_no + +def get_start_pos_iter_window(bits, pattern): + """ + search 'pattern' bit by bit. + + >>> bits = [int(i) for i in "00100000001010101010101010101"] + >>> get_start_pos_iter_window(bits, "01010101") + 9 + + >>> get_start_pos_iter_window([1,2,3], "99") + False + """ + pattern = [int(i) for i in pattern] + for pos, data in enumerate(iter_window(bits, len(pattern))): + if data == pattern: + return pos + break + return False + + +def get_start_pos_iter_steps(bits, pattern): + """ + search 'pattern' in pattern-len-steps. + + 01010101 + 01010101 + >>> bits = [int(i) for i in "0000000001010101"] + >>> get_start_pos_iter_window(bits, "01010101") + 8 + >>> get_start_pos_iter_steps(bits, "01010101") + 8 + + >>> get_start_pos_iter_steps([1,2,3], "99") + False + """ + pattern_len = len(pattern) + pattern = [int(i) for i in pattern] + for pos, data in enumerate(iter_steps(bits, pattern_len)): + if data == pattern: + return pos*pattern_len + break + return False + + +def get_last_pos_iter_steps(bits, pattern): + """ + 01010101 + 01010101 + >>> bits = [int(i) for i in "0101010101010101111000"] + >>> get_last_pos_iter_steps(bits, "01010101") + 16 + + >>> get_last_pos_iter_steps([1,2,3], "99") + 0 + + >>> get_last_pos_iter_steps([0,1,0,1], "01") + 4 + """ + pattern_len = len(pattern) + pattern = [int(i) for i in pattern] + for pos, data in enumerate(iter_steps(bits, pattern_len),1): + if data != pattern: + pos -= 1 + break + return pos*pattern_len + + +def print_bitlist(bit_list): + in_line_count = 0 + for block in iter_steps(bit_list, steps=8): + print "".join([str(i) for i in block]), + in_line_count += 1 + if in_line_count>=DISPLAY_BLOCK_COUNT: + in_line_count = 0 + print + if in_line_count>0: + print + + +def strip_pattern(bit_list, pattern): + end = get_last_pos_iter_steps(bit_list, pattern) + if end: + return bit_list[end:], end + return (bit_list, False) + + +def get_block(bit_list, pattern): + """ + >>> bits = [int(i) for i in "0101010100110101"] + >>> get_block(bits, "0101") + (8, 4, [0, 0, 1, 1], [0, 1, 0, 1]) + + >>> bits = [int(i) for i in "01010011"] + >>> get_block(bits, "0101") + (4, False, [0, 0, 1, 1], []) + + >>> bits = [int(i) for i in "00110101"] + >>> get_block(bits, "0101") + (False, 4, [0, 0, 1, 1], [0, 1, 0, 1]) + + >>> bits = [int(i) for i in "0011"] + >>> get_block(bits, "0101") + (False, False, [0, 0, 1, 1], []) + """ + + bit_list, block_start = strip_pattern(bit_list, pattern) + block_end = get_start_pos_iter_steps(bit_list, pattern) + if not block_end: + block_data = bit_list + cut_bit_list = [] + else: + block_data = bit_list[:block_end] + cut_bit_list = bit_list[block_end:] + + return block_start, block_end, block_data, cut_bit_list + + + if __name__ == "__main__": import doctest print doctest.testmod( verbose=False #~ verbose=True ) + #~ sys.exit() # created by Xroar Emulator -# FILENAME = "HelloWorld1 xroar.wav" -# even_odd = False + FILENAME = "HelloWorld1 xroar.wav" + even_odd = False # created by origin Dragon 32 machine - FILENAME = "HelloWorld1 origin.wav" - even_odd = True + #~ FILENAME = "HelloWorld1 origin.wav" + #~ even_odd = True print "Read '%s'..." % FILENAME @@ -149,13 +322,75 @@ def iter_bits(wavefile, even_odd): frame_count = wavefile.getnframes() print "Numer of audio frames:", frame_count - line = "" - for bit_count, bit in enumerate(iter_bits(wavefile, even_odd)): + #~ line = "" + #~ for bit_count, bit in enumerate(iter_bits(wavefile, even_odd)): #~ if frame_no>100: #~ break - line += str(bit) - if len(line)>70: - print line - line = "" + #~ line += str(bit) + #~ if len(line)>70: + #~ print line + #~ line = "" + + print "read..." + bit_list = list(iter_bits(wavefile, even_odd)) + print "%i bits decoded." % len(bit_list) + + #~ line = "" + #~ for bit_count, bit in enumerate(bit_list): + #~ line += str(bit) + #~ if len(line)>70: + #~ print line + #~ line = "" + #~ print line + #~ print "-"*79 + + START_LEADER = "01010101" + + + start_leader_start = get_start_pos_iter_window(bit_list, START_LEADER) + if not start_leader_start: + print "ERROR: Start leader '%s' not found!" % START_LEADER + sys.exit(-1) + print "Start leader '%s' found at position: %i" % (START_LEADER, start_leader_start) + + # Cut bits before the first 01010101 start leader + print "bits before header:", "".join([str(i) for i in bit_list[:start_leader_start]]) + bit_list = bit_list[start_leader_start:] + + + #~ print "-"*79 + #~ print_bitlist(bit_list) + #~ print "-"*79 + + + # file info block + block_start, block_end, fileinfo_block, bit_list = get_block(bit_list, START_LEADER) + print "Block pos: %i-%i len: %ibits rest: %ibits" % ( + block_start, block_end, len(fileinfo_block), len(bit_list) + ) + + print "-"*79 + print " *** file info block data:" + print_bitlist(fileinfo_block) + print "-"*79 + + + # get data blocks + block_no = 0 + while True: + block_no += 1 + print " *** data block %i" % block_no + block_start, block_end, block_data, bit_list = get_block(bit_list, START_LEADER) + print " Block pos: %i-%i len: %ibits rest: %ibits" % ( + block_start, block_end, len(fileinfo_block), len(bit_list) + ) + print_bitlist(block_data) + print "-"*79 + + if len(block_data) == 0 or len(bit_list)==0: + # no data left + if bit_list: + print "Rest data:" + print_bitlist(bit_list) + break - print "%i bits decoded." % bit_count From e2177d5b9ad4ba16d85e8e000ac36e990a11aab4 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Fri, 16 Aug 2013 14:47:33 +0200 Subject: [PATCH 003/151] I see a HELLO WORLD! ;) --- dragon32_CAS_decode.py | 67 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 58 insertions(+), 9 deletions(-) diff --git a/dragon32_CAS_decode.py b/dragon32_CAS_decode.py index d79fd675..44de4598 100755 --- a/dragon32_CAS_decode.py +++ b/dragon32_CAS_decode.py @@ -13,16 +13,19 @@ http://www.onastick.clara.net/cosio.htm http://www.cs.unc.edu/~yakowenk/coco/text/tapeformat.html + Many thanks to the people from: + http://www.python-forum.de/viewtopic.php?f=1&t=32102 (de) + http://archive.worldofdragon.org/phpBB3/viewtopic.php?f=8&t=4231 (en) + :copyleft: 2013 by Jens Diemer :license: GNU GPL v3 or above, see LICENSE for more details. """ + import collections -import itertools import wave import sys import struct -import audioop ONE_HZ = 2400 # "1" is a single cycle at 2400 Hz @@ -166,7 +169,6 @@ def iter_bits(wavefile, even_odd): frame_count = frame_no-previous_frame_no hz=framerate/frame_count - ms=float(frame_no)/framerate dst_one = abs(ONE_HZ-hz) dst_nul = abs(NUL_HZ-hz) @@ -175,7 +177,8 @@ def iter_bits(wavefile, even_odd): else: yield 0 - #~ print "***", bit, hz, "Hz", "%0.5fms" % ms + # ms=float(frame_no)/framerate + # print "***", bit, hz, "Hz", "%0.5fms" % ms previous_frame_no = frame_no @@ -249,7 +252,7 @@ def get_last_pos_iter_steps(bits, pattern): def print_bitlist(bit_list): in_line_count = 0 for block in iter_steps(bit_list, steps=8): - print "".join([str(i) for i in block]), + print list2str(block), in_line_count += 1 if in_line_count>=DISPLAY_BLOCK_COUNT: in_line_count = 0 @@ -295,6 +298,40 @@ def get_block(bit_list, pattern): return block_start, block_end, block_data, cut_bit_list +def list2str(l): + """ + >>> list2str([0, 0, 0, 1, 0, 0, 1, 0]) + '00010010' + """ + return "".join([str(c) for c in l]) + +def bits2ASCII(bits): + """ + >>> c = bits2ASCII([0, 0, 0, 1, 0, 0, 1, 0]) + >>> c + 72 + >>> chr(c) + 'H' + + >>> bits2ASCII([0, 0, 1, 1, 0, 0, 1, 0]) + 76 + """ + bits = bits[::-1] + bits = list2str(bits) + return int(bits,2) + + +def block2ascii(bit_list): + """ + http://wiki.python.org/moin/BitwiseOperators + """ + for block in iter_steps(bit_list, steps=8): + byte_no = bits2ASCII(block) + character = chr(byte_no) + print "%s %4s %3s %s" % ( + list2str(block), hex(byte_no), byte_no, repr(character) + ) + if __name__ == "__main__": @@ -310,11 +347,12 @@ def get_block(bit_list, pattern): FILENAME = "HelloWorld1 xroar.wav" even_odd = False - # created by origin Dragon 32 machine #~ FILENAME = "HelloWorld1 origin.wav" #~ even_odd = True - + #~ FILENAME = "test.wav" + #~ even_odd = True + #~ even_odd = False print "Read '%s'..." % FILENAME wavefile = wave.open(FILENAME, "r") @@ -335,6 +373,13 @@ def get_block(bit_list, pattern): bit_list = list(iter_bits(wavefile, even_odd)) print "%i bits decoded." % len(bit_list) + #~ print "-"*79 + #~ print_bitlist(bit_list) + #~ print "-"*79 + #~ block2ascii(bit_list) + #~ print "-"*79 + #~ sys.exit() + #~ line = "" #~ for bit_count, bit in enumerate(bit_list): #~ line += str(bit) @@ -344,7 +389,7 @@ def get_block(bit_list, pattern): #~ print line #~ print "-"*79 - START_LEADER = "01010101" + START_LEADER = "10101010" start_leader_start = get_start_pos_iter_window(bit_list, START_LEADER) @@ -354,7 +399,7 @@ def get_block(bit_list, pattern): print "Start leader '%s' found at position: %i" % (START_LEADER, start_leader_start) # Cut bits before the first 01010101 start leader - print "bits before header:", "".join([str(i) for i in bit_list[:start_leader_start]]) + print "bits before header:", repr(list2str(bit_list[:start_leader_start])) bit_list = bit_list[start_leader_start:] @@ -373,6 +418,8 @@ def get_block(bit_list, pattern): print " *** file info block data:" print_bitlist(fileinfo_block) print "-"*79 + block2ascii(fileinfo_block) + print "-"*79 # get data blocks @@ -386,6 +433,8 @@ def get_block(bit_list, pattern): ) print_bitlist(block_data) print "-"*79 + block2ascii(block_data) + print "-"*79 if len(block_data) == 0 or len(bit_list)==0: # no data left From df19264f104cb85c2551d05d18707f4c70a8a77c Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Fri, 16 Aug 2013 16:23:22 +0200 Subject: [PATCH 004/151] Add BASIC_TOKENS dict and use it. Add TEST_STR and try to find it. --- dragon32_CAS_decode.py | 127 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 121 insertions(+), 6 deletions(-) diff --git a/dragon32_CAS_decode.py b/dragon32_CAS_decode.py index 44de4598..49a90bfe 100755 --- a/dragon32_CAS_decode.py +++ b/dragon32_CAS_decode.py @@ -34,6 +34,86 @@ DISPLAY_BLOCK_COUNT = 8 # How many bit block should be printet in one line? +BASIC_TOKENS = { + 128: " FOR ", # 0x80 + 129: " GO ", # 0x81 + 130: " REM ", # 0x82 + 131: "'", # 0x83 + 132: " ELSE ", # 0x84 + 133: " IF ", # 0x85 + 134: " DATA ", # 0x86 + 135: " PRINT ", # 0x87 + 136: " ON ", # 0x88 + 137: " INPUT ", # 0x89 + 138: " END ", # 0x8a + 139: " NEXT ", # 0x8b + 140: " DIM ", # 0x8c + 141: " READ ", # 0x8d + 142: " LET ", # 0x8e + 143: " RUN ", # 0x8f + 144: " RESTORE ", # 0x90 + 145: " RETURN ", # 0x91 + 146: " STOP ", # 0x92 + 147: " POKE ", # 0x93 + 148: " CONT ", # 0x94 + 149: " LIST ", # 0x95 + 150: " CLEAR ", # 0x96 + 151: " NEW ", # 0x97 + 152: " DEF ", # 0x98 + 153: " CLOAD ", # 0x99 + 154: " CSAVE ", # 0x9a + 155: " OPEN ", # 0x9b + 156: " CLOSE ", # 0x9c + 157: " LLIST ", # 0x9d + 158: " SET ", # 0x9e + 159: " RESET ", # 0x9f + 160: " CLS ", # 0xa0 + 161: " MOTOR ", # 0xa1 + 162: " SOUND ", # 0xa2 + 163: " AUDIO ", # 0xa3 + 164: " EXEC ", # 0xa4 + 165: " SKIPF ", # 0xa5 + 166: " DELETE ", # 0xa6 + 167: " EDIT ", # 0xa7 + 168: " TRON ", # 0xa8 + 169: " TROFF ", # 0xa9 + 170: " LINE ", # 0xaa + 171: " PCLS ", # 0xab + 172: " PSET ", # 0xac + 173: " PRESET ", # 0xad + 174: " SCREEN ", # 0xae + 175: " PCLEAR ", # 0xaf + 176: " COLOR ", # 0xb0 + 177: " CIRCLE ", # 0xb1 + 178: " PAINT ", # 0xb2 + 179: " GET ", # 0xb3 + 180: " PUT ", # 0xb4 + 181: " DRAW ", # 0xb5 + 182: " PCOPY ", # 0xb6 + 183: " PMODE ", # 0xb7 + 184: " PLAY ", # 0xb8 + 185: " DLOAD ", # 0xb9 + 186: " RENUM ", # 0xba + 187: " TAB(", # 0xbb + 188: " TO ", # 0xbc + 189: " SUB ", # 0xbd + 190: " FN ", # 0xbe + 191: " THEN ", # 0xbf + 192: " NOT ", # 0xc0 + 193: " STEP ", # 0xc1 + 194: " OFF ", # 0xc2 + 195: "+", # 0xc3 + 196: "-", # 0xc4 + 197: "*", # 0xc5 + 198: "/", # 0xc6 + 199: "^", # 0xc7 + 200: " AND ", # 0xc8 + 201: " OR ", # 0xc9 + 202: ">", # 0xca + 203: "=", # 0xcb + 204: "<", # 0xcc + 205: " USING ", # 0xcd +} def iter_steps(g, steps): """ @@ -325,9 +405,15 @@ def block2ascii(bit_list): """ http://wiki.python.org/moin/BitwiseOperators """ + txt = "" for block in iter_steps(bit_list, steps=8): byte_no = bits2ASCII(block) - character = chr(byte_no) + + if byte_no in BASIC_TOKENS: + character = BASIC_TOKENS[byte_no] + else: + character = chr(byte_no) + print "%s %4s %3s %s" % ( list2str(block), hex(byte_no), byte_no, repr(character) ) @@ -350,9 +436,19 @@ def block2ascii(bit_list): # created by origin Dragon 32 machine #~ FILENAME = "HelloWorld1 origin.wav" #~ even_odd = True - #~ FILENAME = "test.wav" - #~ even_odd = True - #~ even_odd = False + + + """ + The origin BASIC code of the two WAV file is: + + 10 FOR I = 1 TO 10 + 20 PRINT I;"HELLO WORLD!" + 30 NEXT I + + The WAV files are here: + https://github.com/jedie/python-code-snippets/raw/master/CodeSnippets/Dragon%2032/HelloWorld1%20origin.wav + https://github.com/jedie/python-code-snippets/raw/master/CodeSnippets/Dragon%2032/HelloWorld1%20xroar.wav + """ print "Read '%s'..." % FILENAME wavefile = wave.open(FILENAME, "r") @@ -362,17 +458,36 @@ def block2ascii(bit_list): #~ line = "" #~ for bit_count, bit in enumerate(iter_bits(wavefile, even_odd)): - #~ if frame_no>100: - #~ break #~ line += str(bit) #~ if len(line)>70: #~ print line #~ line = "" + #~ sys.exit() print "read..." bit_list = list(iter_bits(wavefile, even_odd)) print "%i bits decoded." % len(bit_list) + + # Test String of binary represtation of "HELLO WORLD!" + TEST_STR=( + "00010010" # 0x48 72 'H' + "10100010" # 0x45 69 'E' + "00110010" # 0x4c 76 'L' + "00110010" # 0x4c 76 'L' + "11110010" # 0x4f 79 'O' + "00000100" # 0x20 32 ' ' + "11101010" # 0x57 87 'W' + "11110010" # 0x4f 79 'O' + "01001010" # 0x52 82 'R' + "00110010" # 0x4c 76 'L' + "00100010" # 0x44 68 'D' + "10000100" # 0x21 33 '!' + )# 000100101010001000110010001100101111001000000100111010101111001001001010001100100010001010000100 + test_start = get_start_pos_iter_window(bit_list, TEST_STR) + print "*** Test String found at:", test_start + + #~ print "-"*79 #~ print_bitlist(bit_list) #~ print "-"*79 From 7d9dc7b1cac92bdb3dc3f23b84c49afec978e7f3 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Fri, 16 Aug 2013 23:59:55 +0200 Subject: [PATCH 005/151] Use sync byte to get the real start of a block. Get and use block type/size, too. --- dragon32_CAS_decode.py | 615 ++++++++++++++++++++++++----------------- 1 file changed, 360 insertions(+), 255 deletions(-) diff --git a/dragon32_CAS_decode.py b/dragon32_CAS_decode.py index 49a90bfe..4c99d917 100755 --- a/dragon32_CAS_decode.py +++ b/dragon32_CAS_decode.py @@ -30,99 +30,118 @@ ONE_HZ = 2400 # "1" is a single cycle at 2400 Hz NUL_HZ = 1200 # "0" is a single cycle at 1200 Hz + +LEADER_BYTE = "10101010" # 0x55 +SYNC_BYTE = "00111100" # 0x3C + +# Block types: +FILENAME_BLOCK = 0x00 +DATA_BLOCK = 0x01 +EOF_BLOCK = 0xff + +BLOCK_TYPE_DICT = { + FILENAME_BLOCK: "filename block", + DATA_BLOCK: "data block", + EOF_BLOCK: "end-of-file block", +} + MIN_TOGGLE_COUNT = 3 # How many samples must be in pos/neg to count a cycle? DISPLAY_BLOCK_COUNT = 8 # How many bit block should be printet in one line? BASIC_TOKENS = { - 128: " FOR ", # 0x80 - 129: " GO ", # 0x81 - 130: " REM ", # 0x82 - 131: "'", # 0x83 - 132: " ELSE ", # 0x84 - 133: " IF ", # 0x85 - 134: " DATA ", # 0x86 - 135: " PRINT ", # 0x87 - 136: " ON ", # 0x88 - 137: " INPUT ", # 0x89 - 138: " END ", # 0x8a - 139: " NEXT ", # 0x8b - 140: " DIM ", # 0x8c - 141: " READ ", # 0x8d - 142: " LET ", # 0x8e - 143: " RUN ", # 0x8f + 128: " FOR ", # 0x80 + 129: " GO ", # 0x81 + 130: " REM ", # 0x82 + 131: "'", # 0x83 + 132: " ELSE ", # 0x84 + 133: " IF ", # 0x85 + 134: " DATA ", # 0x86 + 135: " PRINT ", # 0x87 + 136: " ON ", # 0x88 + 137: " INPUT ", # 0x89 + 138: " END ", # 0x8a + 139: " NEXT ", # 0x8b + 140: " DIM ", # 0x8c + 141: " READ ", # 0x8d + 142: " LET ", # 0x8e + 143: " RUN ", # 0x8f 144: " RESTORE ", # 0x90 - 145: " RETURN ", # 0x91 - 146: " STOP ", # 0x92 - 147: " POKE ", # 0x93 - 148: " CONT ", # 0x94 - 149: " LIST ", # 0x95 - 150: " CLEAR ", # 0x96 - 151: " NEW ", # 0x97 - 152: " DEF ", # 0x98 - 153: " CLOAD ", # 0x99 - 154: " CSAVE ", # 0x9a - 155: " OPEN ", # 0x9b - 156: " CLOSE ", # 0x9c - 157: " LLIST ", # 0x9d - 158: " SET ", # 0x9e - 159: " RESET ", # 0x9f - 160: " CLS ", # 0xa0 - 161: " MOTOR ", # 0xa1 - 162: " SOUND ", # 0xa2 - 163: " AUDIO ", # 0xa3 - 164: " EXEC ", # 0xa4 - 165: " SKIPF ", # 0xa5 - 166: " DELETE ", # 0xa6 - 167: " EDIT ", # 0xa7 - 168: " TRON ", # 0xa8 - 169: " TROFF ", # 0xa9 - 170: " LINE ", # 0xaa - 171: " PCLS ", # 0xab - 172: " PSET ", # 0xac - 173: " PRESET ", # 0xad - 174: " SCREEN ", # 0xae - 175: " PCLEAR ", # 0xaf - 176: " COLOR ", # 0xb0 - 177: " CIRCLE ", # 0xb1 - 178: " PAINT ", # 0xb2 - 179: " GET ", # 0xb3 - 180: " PUT ", # 0xb4 - 181: " DRAW ", # 0xb5 - 182: " PCOPY ", # 0xb6 - 183: " PMODE ", # 0xb7 - 184: " PLAY ", # 0xb8 - 185: " DLOAD ", # 0xb9 - 186: " RENUM ", # 0xba - 187: " TAB(", # 0xbb - 188: " TO ", # 0xbc - 189: " SUB ", # 0xbd - 190: " FN ", # 0xbe - 191: " THEN ", # 0xbf - 192: " NOT ", # 0xc0 - 193: " STEP ", # 0xc1 - 194: " OFF ", # 0xc2 - 195: "+", # 0xc3 - 196: "-", # 0xc4 - 197: "*", # 0xc5 - 198: "/", # 0xc6 - 199: "^", # 0xc7 - 200: " AND ", # 0xc8 - 201: " OR ", # 0xc9 - 202: ">", # 0xca - 203: "=", # 0xcb - 204: "<", # 0xcc - 205: " USING ", # 0xcd + 145: " RETURN ", # 0x91 + 146: " STOP ", # 0x92 + 147: " POKE ", # 0x93 + 148: " CONT ", # 0x94 + 149: " LIST ", # 0x95 + 150: " CLEAR ", # 0x96 + 151: " NEW ", # 0x97 + 152: " DEF ", # 0x98 + 153: " CLOAD ", # 0x99 + 154: " CSAVE ", # 0x9a + 155: " OPEN ", # 0x9b + 156: " CLOSE ", # 0x9c + 157: " LLIST ", # 0x9d + 158: " SET ", # 0x9e + 159: " RESET ", # 0x9f + 160: " CLS ", # 0xa0 + 161: " MOTOR ", # 0xa1 + 162: " SOUND ", # 0xa2 + 163: " AUDIO ", # 0xa3 + 164: " EXEC ", # 0xa4 + 165: " SKIPF ", # 0xa5 + 166: " DELETE ", # 0xa6 + 167: " EDIT ", # 0xa7 + 168: " TRON ", # 0xa8 + 169: " TROFF ", # 0xa9 + 170: " LINE ", # 0xaa + 171: " PCLS ", # 0xab + 172: " PSET ", # 0xac + 173: " PRESET ", # 0xad + 174: " SCREEN ", # 0xae + 175: " PCLEAR ", # 0xaf + 176: " COLOR ", # 0xb0 + 177: " CIRCLE ", # 0xb1 + 178: " PAINT ", # 0xb2 + 179: " GET ", # 0xb3 + 180: " PUT ", # 0xb4 + 181: " DRAW ", # 0xb5 + 182: " PCOPY ", # 0xb6 + 183: " PMODE ", # 0xb7 + 184: " PLAY ", # 0xb8 + 185: " DLOAD ", # 0xb9 + 186: " RENUM ", # 0xba + 187: " TAB(", # 0xbb + 188: " TO ", # 0xbc + 189: " SUB ", # 0xbd + 190: " FN ", # 0xbe + 191: " THEN ", # 0xbf + 192: " NOT ", # 0xc0 + 193: " STEP ", # 0xc1 + 194: " OFF ", # 0xc2 + 195: "+", # 0xc3 + 196: "-", # 0xc4 + 197: "*", # 0xc5 + 198: "/", # 0xc6 + 199: "^", # 0xc7 + 200: " AND ", # 0xc8 + 201: " OR ", # 0xc9 + 202: ">", # 0xca + 203: "=", # 0xcb + 204: "<", # 0xcc + 205: " USING ", # 0xcd } def iter_steps(g, steps): """ - >>> for v in iter_steps([1,2,3,4], steps=2): v + iterate over 'g' in blocks with a length of the given 'step' count. + + >>> for v in iter_steps([1,2,3,4,5], steps=2): v [1, 2] [3, 4] - >>> for v in iter_steps([1,2,3,4,5,6], steps=3): v + [5] + >>> for v in iter_steps([1,2,3,4,5,6,7,8,9], steps=3): v [1, 2, 3] [4, 5, 6] + [7, 8, 9] 12345678 12345678 12345678 @@ -135,35 +154,37 @@ def iter_steps(g, steps): values = [] for value in g: values.append(value) - if len(values)==steps: + if len(values) == steps: yield list(values) values = [] if values: yield list(values) -def iter_window(g, steps): +def iter_window(g, window_size): """ - >>> for v in iter_window([1,2,3,4], steps=2): v + interate over 'g' bit-by-bit and yield a window with the given 'window_size' width. + + >>> for v in iter_window([1,2,3,4], window_size=2): v [1, 2] [2, 3] [3, 4] - >>> for v in iter_window([1,2,3,4,5], steps=3): v + >>> for v in iter_window([1,2,3,4,5], window_size=3): v [1, 2, 3] [2, 3, 4] [3, 4, 5] - >>> for v in iter_window([1,2,3,4], steps=2): + >>> for v in iter_window([1,2,3,4], window_size=2): ... v ... v.append(True) [1, 2] [2, 3] [3, 4] """ - values = collections.deque(maxlen=steps) + values = collections.deque(maxlen=window_size) for value in g: values.append(value) - if len(values)==steps: + if len(values) == window_size: yield list(values) @@ -177,9 +198,9 @@ def count_sign(values): positive_count = 0 negative_count = 0 for value in values: - if value>0: + if value > 0: positive_count += 1 - elif value<0: + elif value < 0: negative_count += 1 return positive_count, negative_count @@ -195,7 +216,7 @@ def iter_wave_values(wavefile): frame_count = wavefile.getnframes() # number of audio frames # FIXME - if samplewidth==1: + if samplewidth == 1: struct_unpack_str = "b" elif samplewidth == 2: struct_unpack_str = "=MIN_TOGGLE_COUNT: + if len(window_values) >= MIN_TOGGLE_COUNT: positive_count, negative_count = count_sign(window_values) - #~ print window_values, positive_count, negative_count - if not in_positive and positive_count==MIN_TOGGLE_COUNT and negative_count==0: + # print window_values, positive_count, negative_count + if not in_positive and positive_count == MIN_TOGGLE_COUNT and negative_count == 0: # go into a positive sinus area in_positive = True in_negative = False toggle_count += 1 - elif not in_negative and negative_count==MIN_TOGGLE_COUNT and positive_count==0: + elif not in_negative and negative_count == MIN_TOGGLE_COUNT and positive_count == 0: # go into a negative sinus area in_negative = True in_positive = False toggle_count += 1 - if toggle_count>=2: + if toggle_count >= 2: # a single sinus cycle complete toggle_count = 0 - frame_count = frame_no-previous_frame_no - hz=framerate/frame_count + frame_count = frame_no - previous_frame_no + hz = framerate / frame_count - dst_one = abs(ONE_HZ-hz) - dst_nul = abs(NUL_HZ-hz) - if dst_one>> bits = [int(i) for i in "00100000001010101010101010101"] >>> get_start_pos_iter_window(bits, "01010101") @@ -284,7 +306,7 @@ def get_start_pos_iter_window(bits, pattern): def get_start_pos_iter_steps(bits, pattern): """ - search 'pattern' in pattern-len-steps. + search 'pattern' in pattern length iterations (iter steps) 01010101 01010101 @@ -301,82 +323,193 @@ def get_start_pos_iter_steps(bits, pattern): pattern = [int(i) for i in pattern] for pos, data in enumerate(iter_steps(bits, pattern_len)): if data == pattern: - return pos*pattern_len + return pos * pattern_len break return False -def get_last_pos_iter_steps(bits, pattern): +def count_continuous_pattern(bits, pattern): """ - 01010101 - 01010101 - >>> bits = [int(i) for i in "0101010101010101111000"] - >>> get_last_pos_iter_steps(bits, "01010101") - 16 - - >>> get_last_pos_iter_steps([1,2,3], "99") + count 'pattern' matches without ceasing. + + >>> bit_str = ( + ... "00111100" + ... "00111100" + ... "0101") + >>> pos = count_continuous_pattern([int(i) for i in bit_str], "00111100") + >>> bit_str[pos*8:] + '0101' + >>> pos + 2 + + >>> count_continuous_pattern([1,1,1,2,3], "1") + 3 + + >>> count_continuous_pattern([1,2,3], "99") 0 - >>> get_last_pos_iter_steps([0,1,0,1], "01") - 4 + >>> count_continuous_pattern([0,1,0,1], "01") + 2 """ pattern_len = len(pattern) pattern = [int(i) for i in pattern] - for pos, data in enumerate(iter_steps(bits, pattern_len),1): + for count, data in enumerate(iter_steps(bits, pattern_len), 1): if data != pattern: - pos -= 1 + count -= 1 break - return pos*pattern_len - + return count -def print_bitlist(bit_list): - in_line_count = 0 - for block in iter_steps(bit_list, steps=8): - print list2str(block), - in_line_count += 1 - if in_line_count>=DISPLAY_BLOCK_COUNT: - in_line_count = 0 - print - if in_line_count>0: - print +def find_iter_window(bit_list, pattern): + """ + Search for 'pattern' in bit-by-bit steps (iter window) + and return the number of bits before the 'pattern' match. -def strip_pattern(bit_list, pattern): - end = get_last_pos_iter_steps(bit_list, pattern) - if end: - return bit_list[end:], end - return (bit_list, False) + Useable for slicing all bits before the first 'pattern' match: + >>> bit_str = "111010111" + >>> pos = find_iter_window([int(i) for i in bit_str], "010") + >>> bit_str[pos:] + '010111' + >>> pos + 3 -def get_block(bit_list, pattern): + >>> find_iter_window([1,1,1], "0") + 0 + >>> find_iter_window([1,0,0], "1") + 0 + >>> find_iter_window([0,1,0], "1") + 1 + >>> find_iter_window([0,0,1], "1") + 2 """ - >>> bits = [int(i) for i in "0101010100110101"] - >>> get_block(bits, "0101") - (8, 4, [0, 0, 1, 1], [0, 1, 0, 1]) - - >>> bits = [int(i) for i in "01010011"] - >>> get_block(bits, "0101") - (4, False, [0, 0, 1, 1], []) - - >>> bits = [int(i) for i in "00110101"] - >>> get_block(bits, "0101") - (False, 4, [0, 0, 1, 1], [0, 1, 0, 1]) + pattern_len = len(pattern) + pattern = [int(i) for i in pattern] + for pos, data in enumerate(iter_window(bit_list, pattern_len)): + if data == pattern: + return pos + return 0 - >>> bits = [int(i) for i in "0011"] - >>> get_block(bits, "0101") - (False, False, [0, 0, 1, 1], []) +def pop_bytes_from_bit_list(bit_list, count): + """ + >>> bit_str = ( + ... "00110011" + ... "00001111" + ... "01010101" + ... "11001100") + >>> bit_list = [int(i) for i in bit_str] + >>> bit_list, bytes = pop_bytes_from_bit_list(bit_list, 1) + >>> bytes + [[0, 0, 1, 1, 0, 0, 1, 1]] + >>> bit_list, bytes = pop_bytes_from_bit_list(bit_list, 2) + >>> bytes + [[0, 0, 0, 0, 1, 1, 1, 1], [0, 1, 0, 1, 0, 1, 0, 1]] + >>> bit_list, bytes = pop_bytes_from_bit_list(bit_list, 1) + >>> bytes + [[1, 1, 0, 0, 1, 1, 0, 0]] """ + data_bit_count = count*8 + + data_bit_list = bit_list[:data_bit_count] + data = list(iter_steps(data_bit_list, steps=8)) + + bit_list = bit_list[data_bit_count:] + return bit_list, data + + + + + + +# def goto_next_block(bit_list, debug=False): + # """ + # Cut the bit_list until leader+sync byte + + # >>> bits = ( + # ... "10101010" # 0x55 leader byte + # ... "00111100" # 0x3C sync byte + # ... "00010010" # 0x48 'H' + # ... ) + # >>> bit_list = [int(i) for i in bits] + # >>> bit_list = goto_next_block(bit_list) + # >>> bit_list + # (8, 8, [0, 0, 0, 1, 0, 0, 1, 0]) + + # more bits inserted: + # >>> bits = ("1010" # inserted + # ... "101010100011110000010010") + # >>> goto_next_block([int(i) for i in bits]) + # (8, 12, [0, 0, 0, 1, 0, 0, 1, 0]) + + # no complete leader byte + # >>> bits = ("1010" # incomplete + # ... "0011110000010010") + # >>> goto_next_block([int(i) for i in bits]) + # (0, 12, [0, 0, 0, 1, 0, 0, 1, 0]) + # """ + # bit_list, leader_end_pos = strip_pattern_iter_steps(bit_list, LEADER_BYTE) + # bit_list, sync_end_pos = strip_pattern_iter_window(bit_list, SYNC_BYTE) + # return leader_end_pos, sync_end_pos, bit_list + + + +# def get_block(bit_list, pattern): + # """ + # >>> bits = ( + # ... "10101010" # 0x55 leader byte + # ... "00111100" # 0x3C sync byte + # ... "00010010" # 0x48 'H' + # ... ) + # >>> bit_list = [int(i) for i in bits] + # >>> bit_list = goto_next_block(bit_list) + # >>> bit_list + # (8, 0, [0, 0, 0, 1, 0, 0, 1, 0]) + + # >>> bits = [int(i) for i in "0101010100110101"] + # >>> get_block(bits, "0101") + # (8, 4, [0, 0, 1, 1], [0, 1, 0, 1]) + + # >>> bits = [int(i) for i in "01010011"] + # >>> get_block(bits, "0101") + # (4, False, [0, 0, 1, 1], []) + + # >>> bits = [int(i) for i in "00110101"] + # >>> get_block(bits, "0101") + # (False, 4, [0, 0, 1, 1], [0, 1, 0, 1]) + + # >>> bits = [int(i) for i in "0011"] + # >>> get_block(bits, "0101") + # (False, False, [0, 0, 1, 1], []) + # """ + # block_end = get_start_pos_iter_steps(bit_list, pattern) + # if not block_end: + # block_data = bit_list + # cut_bit_list = [] + # else: + # block_data = bit_list[:block_end] + # cut_bit_list = bit_list[block_end:] + + # return block_end, block_data, cut_bit_list + + +def print_block_bit_list(block_bit_list): + in_line_count = 0 - bit_list, block_start = strip_pattern(bit_list, pattern) - block_end = get_start_pos_iter_steps(bit_list, pattern) - if not block_end: - block_data = bit_list - cut_bit_list = [] - else: - block_data = bit_list[:block_end] - cut_bit_list = bit_list[block_end:] + line = "" + for no, block in enumerate(block_bit_list, -DISPLAY_BLOCK_COUNT + 1): + line += "%s " % list2str(block) + in_line_count += 1 + if in_line_count >= DISPLAY_BLOCK_COUNT: + in_line_count = 0 + print "%4s - %s" % (no, line) + line = "" + if in_line_count > 0: + print + +def print_bitlist(bit_list): + block_bit_list = iter_steps(bit_list, steps=8) + print_block_bit_list(block_bit_list) - return block_start, block_end, block_data, cut_bit_list def list2str(l): """ @@ -385,6 +518,7 @@ def list2str(l): """ return "".join([str(c) for c in l]) + def bits2ASCII(bits): """ >>> c = bits2ASCII([0, 0, 0, 1, 0, 0, 1, 0]) @@ -398,15 +532,11 @@ def bits2ASCII(bits): """ bits = bits[::-1] bits = list2str(bits) - return int(bits,2) + return int(bits, 2) -def block2ascii(bit_list): - """ - http://wiki.python.org/moin/BitwiseOperators - """ - txt = "" - for block in iter_steps(bit_list, steps=8): +def block2ascii(block_bit_list): + for block in block_bit_list: byte_no = bits2ASCII(block) if byte_no in BASIC_TOKENS: @@ -419,14 +549,39 @@ def block2ascii(bit_list): ) +def get_block_info(bit_list): + leader_pos = find_iter_window(bit_list, LEADER_BYTE) # Search for LEADER_BYTE in bit-by-bit steps + print "Start leader '%s' found at position: %i" % (LEADER_BYTE, leader_pos) + + # Cut bits before the first 01010101 start leader + print "bits before header:", repr(list2str(bit_list[:leader_pos])) + bit_list = bit_list[leader_pos:] + + leader_count = count_continuous_pattern(bit_list, LEADER_BYTE) + print "Found %i leader bytes" % leader_count + to_cut = leader_count * 8 + bit_list = bit_list[to_cut:] + + sync_pos = find_iter_window(bit_list, SYNC_BYTE) # Search for SYNC_BYTE in bit-by-bit steps + print "Find sync byte after %i Bits" % sync_pos + to_cut = sync_pos + 8 # Bits before sync byte + sync byte + bit_list = bit_list[to_cut:] + + bit_list, bytes = pop_bytes_from_bit_list(bit_list, count=2) + + block_type = bits2ASCII(bytes[0]) + block_length = bits2ASCII(bytes[1]) + + return bit_list, block_type, block_length + if __name__ == "__main__": import doctest print doctest.testmod( verbose=False - #~ verbose=True + # verbose=True ) - #~ sys.exit() +# sys.exit() # created by Xroar Emulator @@ -434,8 +589,8 @@ def block2ascii(bit_list): even_odd = False # created by origin Dragon 32 machine - #~ FILENAME = "HelloWorld1 origin.wav" - #~ even_odd = True +# FILENAME = "HelloWorld1 origin.wav" +# even_odd = True """ @@ -456,105 +611,55 @@ def block2ascii(bit_list): frame_count = wavefile.getnframes() print "Numer of audio frames:", frame_count - #~ line = "" - #~ for bit_count, bit in enumerate(iter_bits(wavefile, even_odd)): - #~ line += str(bit) - #~ if len(line)>70: - #~ print line - #~ line = "" - #~ sys.exit() + # line = "" + # for bit_count, bit in enumerate(iter_bits(wavefile, even_odd)): + # line += str(bit) + # if len(line)>70: + # print line + # line = "" + # sys.exit() print "read..." bit_list = list(iter_bits(wavefile, even_odd)) print "%i bits decoded." % len(bit_list) + print - # Test String of binary represtation of "HELLO WORLD!" - TEST_STR=( - "00010010" # 0x48 72 'H' - "10100010" # 0x45 69 'E' - "00110010" # 0x4c 76 'L' - "00110010" # 0x4c 76 'L' - "11110010" # 0x4f 79 'O' - "00000100" # 0x20 32 ' ' - "11101010" # 0x57 87 'W' - "11110010" # 0x4f 79 'O' - "01001010" # 0x52 82 'R' - "00110010" # 0x4c 76 'L' - "00100010" # 0x44 68 'D' - "10000100" # 0x21 33 '!' - )# 000100101010001000110010001100101111001000000100111010101111001001001010001100100010001010000100 - test_start = get_start_pos_iter_window(bit_list, TEST_STR) - print "*** Test String found at:", test_start - - - #~ print "-"*79 - #~ print_bitlist(bit_list) - #~ print "-"*79 - #~ block2ascii(bit_list) - #~ print "-"*79 - #~ sys.exit() - - #~ line = "" - #~ for bit_count, bit in enumerate(bit_list): - #~ line += str(bit) - #~ if len(line)>70: - #~ print line - #~ line = "" - #~ print line - #~ print "-"*79 - - START_LEADER = "10101010" - - - start_leader_start = get_start_pos_iter_window(bit_list, START_LEADER) - if not start_leader_start: - print "ERROR: Start leader '%s' not found!" % START_LEADER - sys.exit(-1) - print "Start leader '%s' found at position: %i" % (START_LEADER, start_leader_start) - - # Cut bits before the first 01010101 start leader - print "bits before header:", repr(list2str(bit_list[:start_leader_start])) - bit_list = bit_list[start_leader_start:] - + # print "-"*79 + # print_bitlist(bit_list) + # print "-"*79 + # block2ascii(bit_list) + # print "-"*79 + # sys.exit() - #~ print "-"*79 - #~ print_bitlist(bit_list) - #~ print "-"*79 + # line = "" + # for bit_count, bit in enumerate(bit_list): + # line += str(bit) + # if len(line)>70: + # print line + # line = "" + # print line + # print "-"*79 +# print "-"*79 +# print_bitlist(bit_list) +# print "-"*79 - # file info block - block_start, block_end, fileinfo_block, bit_list = get_block(bit_list, START_LEADER) - print "Block pos: %i-%i len: %ibits rest: %ibits" % ( - block_start, block_end, len(fileinfo_block), len(bit_list) - ) - print "-"*79 - print " *** file info block data:" - print_bitlist(fileinfo_block) - print "-"*79 - block2ascii(fileinfo_block) - print "-"*79 + while True: + print "="*79 + bit_list, block_type, block_length = get_block_info(bit_list) + print "*** block type: 0x%x (%s)" % (block_type, BLOCK_TYPE_DICT[block_type]) + print "*** block length:", block_length + if block_type == EOF_BLOCK: + print "end of file." + break - # get data blocks - block_no = 0 - while True: - block_no += 1 - print " *** data block %i" % block_no - block_start, block_end, block_data, bit_list = get_block(bit_list, START_LEADER) - print " Block pos: %i-%i len: %ibits rest: %ibits" % ( - block_start, block_end, len(fileinfo_block), len(bit_list) - ) - print_bitlist(block_data) - print "-"*79 - block2ascii(block_data) + bit_list, block_bit_list = pop_bytes_from_bit_list(bit_list, count=block_length) + print_block_bit_list(block_bit_list) print "-"*79 + block2ascii(block_bit_list) + print "="*79 - if len(block_data) == 0 or len(bit_list)==0: - # no data left - if bit_list: - print "Rest data:" - print_bitlist(bit_list) - break From e57631b67bbc163f06582c4aa3c64d22dd8e5b24 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Sat, 17 Aug 2013 20:59:32 +0200 Subject: [PATCH 006/151] remove unused code --- dragon32_CAS_decode.py | 148 +---------------------------------------- 1 file changed, 3 insertions(+), 145 deletions(-) diff --git a/dragon32_CAS_decode.py b/dragon32_CAS_decode.py index 4c99d917..e6d6703f 100755 --- a/dragon32_CAS_decode.py +++ b/dragon32_CAS_decode.py @@ -283,55 +283,10 @@ def iter_bits(wavefile, even_odd): previous_frame_no = frame_no - -def get_start_pos_iter_window(bits, pattern): - """ - search 'pattern' in bit-by-bit steps (iter window) - - - >>> bits = [int(i) for i in "00100000001010101010101010101"] - >>> get_start_pos_iter_window(bits, "01010101") - 9 - - >>> get_start_pos_iter_window([1,2,3], "99") - False - """ - pattern = [int(i) for i in pattern] - for pos, data in enumerate(iter_window(bits, len(pattern))): - if data == pattern: - return pos - break - return False - - -def get_start_pos_iter_steps(bits, pattern): - """ - search 'pattern' in pattern length iterations (iter steps) - - 01010101 - 01010101 - >>> bits = [int(i) for i in "0000000001010101"] - >>> get_start_pos_iter_window(bits, "01010101") - 8 - >>> get_start_pos_iter_steps(bits, "01010101") - 8 - - >>> get_start_pos_iter_steps([1,2,3], "99") - False - """ - pattern_len = len(pattern) - pattern = [int(i) for i in pattern] - for pos, data in enumerate(iter_steps(bits, pattern_len)): - if data == pattern: - return pos * pattern_len - break - return False - - def count_continuous_pattern(bits, pattern): """ count 'pattern' matches without ceasing. - + >>> bit_str = ( ... "00111100" ... "00111100" @@ -408,89 +363,14 @@ def pop_bytes_from_bit_list(bit_list, count): >>> bytes [[1, 1, 0, 0, 1, 1, 0, 0]] """ - data_bit_count = count*8 + data_bit_count = count * 8 data_bit_list = bit_list[:data_bit_count] data = list(iter_steps(data_bit_list, steps=8)) - + bit_list = bit_list[data_bit_count:] return bit_list, data - - - - - -# def goto_next_block(bit_list, debug=False): - # """ - # Cut the bit_list until leader+sync byte - - # >>> bits = ( - # ... "10101010" # 0x55 leader byte - # ... "00111100" # 0x3C sync byte - # ... "00010010" # 0x48 'H' - # ... ) - # >>> bit_list = [int(i) for i in bits] - # >>> bit_list = goto_next_block(bit_list) - # >>> bit_list - # (8, 8, [0, 0, 0, 1, 0, 0, 1, 0]) - - # more bits inserted: - # >>> bits = ("1010" # inserted - # ... "101010100011110000010010") - # >>> goto_next_block([int(i) for i in bits]) - # (8, 12, [0, 0, 0, 1, 0, 0, 1, 0]) - - # no complete leader byte - # >>> bits = ("1010" # incomplete - # ... "0011110000010010") - # >>> goto_next_block([int(i) for i in bits]) - # (0, 12, [0, 0, 0, 1, 0, 0, 1, 0]) - # """ - # bit_list, leader_end_pos = strip_pattern_iter_steps(bit_list, LEADER_BYTE) - # bit_list, sync_end_pos = strip_pattern_iter_window(bit_list, SYNC_BYTE) - # return leader_end_pos, sync_end_pos, bit_list - - - -# def get_block(bit_list, pattern): - # """ - # >>> bits = ( - # ... "10101010" # 0x55 leader byte - # ... "00111100" # 0x3C sync byte - # ... "00010010" # 0x48 'H' - # ... ) - # >>> bit_list = [int(i) for i in bits] - # >>> bit_list = goto_next_block(bit_list) - # >>> bit_list - # (8, 0, [0, 0, 0, 1, 0, 0, 1, 0]) - - # >>> bits = [int(i) for i in "0101010100110101"] - # >>> get_block(bits, "0101") - # (8, 4, [0, 0, 1, 1], [0, 1, 0, 1]) - - # >>> bits = [int(i) for i in "01010011"] - # >>> get_block(bits, "0101") - # (4, False, [0, 0, 1, 1], []) - - # >>> bits = [int(i) for i in "00110101"] - # >>> get_block(bits, "0101") - # (False, 4, [0, 0, 1, 1], [0, 1, 0, 1]) - - # >>> bits = [int(i) for i in "0011"] - # >>> get_block(bits, "0101") - # (False, False, [0, 0, 1, 1], []) - # """ - # block_end = get_start_pos_iter_steps(bit_list, pattern) - # if not block_end: - # block_data = bit_list - # cut_bit_list = [] - # else: - # block_data = bit_list[:block_end] - # cut_bit_list = bit_list[block_end:] - - # return block_end, block_data, cut_bit_list - def print_block_bit_list(block_bit_list): in_line_count = 0 @@ -611,14 +491,6 @@ def get_block_info(bit_list): frame_count = wavefile.getnframes() print "Numer of audio frames:", frame_count - # line = "" - # for bit_count, bit in enumerate(iter_bits(wavefile, even_odd)): - # line += str(bit) - # if len(line)>70: - # print line - # line = "" - # sys.exit() - print "read..." bit_list = list(iter_bits(wavefile, even_odd)) print "%i bits decoded." % len(bit_list) @@ -632,20 +504,6 @@ def get_block_info(bit_list): # print "-"*79 # sys.exit() - # line = "" - # for bit_count, bit in enumerate(bit_list): - # line += str(bit) - # if len(line)>70: - # print line - # line = "" - # print line - # print "-"*79 - -# print "-"*79 -# print_bitlist(bit_list) -# print "-"*79 - - while True: print "="*79 bit_list, block_type, block_length = get_block_info(bit_list) From caf868085eba6ba32558d7f5c1071bdccf642d4f Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Sat, 17 Aug 2013 21:01:41 +0200 Subject: [PATCH 007/151] add a filename block class --- dragon32_CAS_decode.py | 55 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/dragon32_CAS_decode.py b/dragon32_CAS_decode.py index e6d6703f..4375152d 100755 --- a/dragon32_CAS_decode.py +++ b/dragon32_CAS_decode.py @@ -345,6 +345,7 @@ def find_iter_window(bit_list, pattern): return pos return 0 + def pop_bytes_from_bit_list(bit_list, count): """ >>> bit_str = ( @@ -399,15 +400,15 @@ def list2str(l): return "".join([str(c) for c in l]) -def bits2ASCII(bits): +def bits2byte_no(bits): """ - >>> c = bits2ASCII([0, 0, 0, 1, 0, 0, 1, 0]) + >>> c = bits2byte_no([0, 0, 0, 1, 0, 0, 1, 0]) >>> c 72 >>> chr(c) 'H' - >>> bits2ASCII([0, 0, 1, 1, 0, 0, 1, 0]) + >>> bits2byte_no([0, 0, 1, 1, 0, 0, 1, 0]) 76 """ bits = bits[::-1] @@ -415,9 +416,14 @@ def bits2ASCII(bits): return int(bits, 2) +def block2bytes(block_bit_list): + bytes = "".join([chr(bits2byte_no(block)) for block in block_bit_list]) + return bytes + + def block2ascii(block_bit_list): for block in block_bit_list: - byte_no = bits2ASCII(block) + byte_no = bits2byte_no(block) if byte_no in BASIC_TOKENS: character = BASIC_TOKENS[byte_no] @@ -449,12 +455,43 @@ def get_block_info(bit_list): bit_list, bytes = pop_bytes_from_bit_list(bit_list, count=2) - block_type = bits2ASCII(bytes[0]) - block_length = bits2ASCII(bytes[1]) + block_type = bits2byte_no(bytes[0]) + block_length = bits2byte_no(bytes[1]) return bit_list, block_type, block_length +class FilenameBlock(object): + """ + 5.1 An 8 byte program name + 5.2 A file ID byte where: + 00=BASIC program + 01=Data file + 03=Binary file + 5.3 An ASCII flag where: + 00=Binary file + FF=ASCII file + 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. + 5.5 Two bytes for the default EXEC address + of a binary file. + 5.6 Two bytes for the default load address + of a binary file. + """ + def __init__(self, block_bit_list): + print_block_bit_list(block_bit_list) + block2ascii(block_bit_list) + + self.data = block2bytes(block_bit_list) + self.filename = self.data[:8] + + def __repr__(self): + return "" % (self.filename, repr(self.data)) + + if __name__ == "__main__": import doctest print doctest.testmod( @@ -515,6 +552,12 @@ def get_block_info(bit_list): break bit_list, block_bit_list = pop_bytes_from_bit_list(bit_list, count=block_length) + + if block_type == FILENAME_BLOCK: + file_block = FilenameBlock(block_bit_list) + print repr(file_block) + continue + print_block_bit_list(block_bit_list) print "-"*79 block2ascii(block_bit_list) From 3f1ba64ca8e00557f0be26f1dac8278c63a47204 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Sat, 17 Aug 2013 23:26:25 +0200 Subject: [PATCH 008/151] parse the file content. Put data into objects. --- dragon32_CAS_decode.py | 121 +++++++++++++++++++++++++++++++++++------ 1 file changed, 104 insertions(+), 17 deletions(-) diff --git a/dragon32_CAS_decode.py b/dragon32_CAS_decode.py index 4375152d..13355f04 100755 --- a/dragon32_CAS_decode.py +++ b/dragon32_CAS_decode.py @@ -421,15 +421,10 @@ def block2bytes(block_bit_list): return bytes -def block2ascii(block_bit_list): +def print_block_table(block_bit_list): for block in block_bit_list: byte_no = bits2byte_no(block) - - if byte_no in BASIC_TOKENS: - character = BASIC_TOKENS[byte_no] - else: - character = chr(byte_no) - + character = chr(byte_no) print "%s %4s %3s %s" % ( list2str(block), hex(byte_no), byte_no, repr(character) ) @@ -461,8 +456,77 @@ def get_block_info(bit_list): return bit_list, block_type, block_length -class FilenameBlock(object): +class CodeLine(object): + def __init__(self, pre_bytes, line_no, code): + assert isinstance(line_no, int), "Line number not integer, it's: %s" % repr(line_no) + self.pre_bytes = pre_bytes + self.line_no = line_no + self.code = code + + def __repr__(self): + return "" % ( + repr(self.pre_bytes), repr(self.line_no), repr(self.code) + ) + + +class FileContent(object): + """ + Content (all data blocks) of a cassette file. + """ + def __init__(self): + self.code_lines = [] + + def add_data_block(self, block_length, block_bit_list): + in_code_line = False + pre_bytes = [] + line_no = None + code_line = "" + + raw_bytes = [bits2byte_no(bit_block) for bit_block in block_bit_list] + for index, byte_no in enumerate(raw_bytes): +# print index, hex(byte_no) + if byte_no == 0x00: + if in_code_line: + # print "Add code line", repr(pre_bytes), repr(line_no), repr(code_line) + code_line_obj = CodeLine(pre_bytes, line_no, code_line) + self.code_lines.append(code_line_obj) + pre_bytes = [] + line_no = None + code_line = "" + in_code_line = False + else: + if raw_bytes[index:] == [0, 0]: + # Next two bytes are 0x00 0x00 -> end of data delimiter + break + in_code_line = True + else: + if in_code_line: + if line_no is None: + line_no = byte_no + continue + + if byte_no in BASIC_TOKENS: + character = BASIC_TOKENS[byte_no].strip() # XXX: strip direct in BASIC_TOKENS ??? + else: + character = chr(byte_no) + code_line += character + else: + pre_bytes.append(byte_no) + + self.print_code_lines() + + def print_code_lines(self): + for code_line in self.code_lines: +# print repr(code_line) + print "pre bytes: %-10s code: %i %s" % ( + repr(code_line.pre_bytes), code_line.line_no, code_line.code + ) + + +class CassetteFile(object): """ + Representes a "file name block" and his "data block" + 5.1 An 8 byte program name 5.2 A file ID byte where: 00=BASIC program @@ -481,17 +545,43 @@ class FilenameBlock(object): 5.6 Two bytes for the default load address of a binary file. """ - def __init__(self, block_bit_list): + def __init__(self, file_block_bit_list): print_block_bit_list(block_bit_list) - block2ascii(block_bit_list) + print_block_table(block_bit_list) self.data = block2bytes(block_bit_list) self.filename = self.data[:8] + self.file_content = FileContent() + + def add_data_block(self, block_length, block_bit_list): + self.file_content.add_data_block(block_length, block_bit_list) + def __repr__(self): return "" % (self.filename, repr(self.data)) +class Cassette(object): + def __init__(self): + self.files = [] + self.current_file = None + + def add_block(self, block_type, block_length, block_bit_list): + if block_type == EOF_BLOCK: + return + elif block_type == FILENAME_BLOCK: + self.current_file = CassetteFile(block_bit_list) + print "Add file %s" % repr(self.current_file) + self.files.append(self.current_file) + elif block_type == DATA_BLOCK: + self.current_file.add_data_block(block_length, block_bit_list) + else: + raise TypeError("Block type %s unkown!" & hex(block_type)) + + + + + if __name__ == "__main__": import doctest print doctest.testmod( @@ -537,10 +627,12 @@ def __repr__(self): # print "-"*79 # print_bitlist(bit_list) # print "-"*79 - # block2ascii(bit_list) + # print_block_table(bit_list) # print "-"*79 # sys.exit() + cassette = Cassette() + while True: print "="*79 bit_list, block_type, block_length = get_block_info(bit_list) @@ -553,14 +645,9 @@ def __repr__(self): bit_list, block_bit_list = pop_bytes_from_bit_list(bit_list, count=block_length) - if block_type == FILENAME_BLOCK: - file_block = FilenameBlock(block_bit_list) - print repr(file_block) - continue + cassette.add_block(block_type, block_length, block_bit_list) print_block_bit_list(block_bit_list) - print "-"*79 - block2ascii(block_bit_list) print "="*79 From 120b1522f556b85ae390ec0be22326c143f6ff7a Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Sun, 18 Aug 2013 01:24:26 +0200 Subject: [PATCH 009/151] add status while reading WAV file. move some stuff in seperate files. --- basic_tokens.py | 90 +++++++++++++++++++++ dragon32_CAS_decode.py | 179 ++++++++++++++++++----------------------- utils.py | 95 ++++++++++++++++++++++ 3 files changed, 264 insertions(+), 100 deletions(-) create mode 100644 basic_tokens.py create mode 100644 utils.py diff --git a/basic_tokens.py b/basic_tokens.py new file mode 100644 index 00000000..93843554 --- /dev/null +++ b/basic_tokens.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python2 + +""" + :copyleft: 2013 by Jens Diemer + :license: GNU GPL v3 or above, see LICENSE for more details. +""" + +BASIC_TOKENS = { + 128: " FOR ", # 0x80 + 129: " GO ", # 0x81 + 130: " REM ", # 0x82 + 131: "'", # 0x83 + 132: " ELSE ", # 0x84 + 133: " IF ", # 0x85 + 134: " DATA ", # 0x86 + 135: " PRINT ", # 0x87 + 136: " ON ", # 0x88 + 137: " INPUT ", # 0x89 + 138: " END ", # 0x8a + 139: " NEXT ", # 0x8b + 140: " DIM ", # 0x8c + 141: " READ ", # 0x8d + 142: " LET ", # 0x8e + 143: " RUN ", # 0x8f + 144: " RESTORE ", # 0x90 + 145: " RETURN ", # 0x91 + 146: " STOP ", # 0x92 + 147: " POKE ", # 0x93 + 148: " CONT ", # 0x94 + 149: " LIST ", # 0x95 + 150: " CLEAR ", # 0x96 + 151: " NEW ", # 0x97 + 152: " DEF ", # 0x98 + 153: " CLOAD ", # 0x99 + 154: " CSAVE ", # 0x9a + 155: " OPEN ", # 0x9b + 156: " CLOSE ", # 0x9c + 157: " LLIST ", # 0x9d + 158: " SET ", # 0x9e + 159: " RESET ", # 0x9f + 160: " CLS ", # 0xa0 + 161: " MOTOR ", # 0xa1 + 162: " SOUND ", # 0xa2 + 163: " AUDIO ", # 0xa3 + 164: " EXEC ", # 0xa4 + 165: " SKIPF ", # 0xa5 + 166: " DELETE ", # 0xa6 + 167: " EDIT ", # 0xa7 + 168: " TRON ", # 0xa8 + 169: " TROFF ", # 0xa9 + 170: " LINE ", # 0xaa + 171: " PCLS ", # 0xab + 172: " PSET ", # 0xac + 173: " PRESET ", # 0xad + 174: " SCREEN ", # 0xae + 175: " PCLEAR ", # 0xaf + 176: " COLOR ", # 0xb0 + 177: " CIRCLE ", # 0xb1 + 178: " PAINT ", # 0xb2 + 179: " GET ", # 0xb3 + 180: " PUT ", # 0xb4 + 181: " DRAW ", # 0xb5 + 182: " PCOPY ", # 0xb6 + 183: " PMODE ", # 0xb7 + 184: " PLAY ", # 0xb8 + 185: " DLOAD ", # 0xb9 + 186: " RENUM ", # 0xba + 187: " TAB(", # 0xbb + 188: " TO ", # 0xbc + 189: " SUB ", # 0xbd + 190: " FN ", # 0xbe + 191: " THEN ", # 0xbf + 192: " NOT ", # 0xc0 + 193: " STEP ", # 0xc1 + 194: " OFF ", # 0xc2 + 195: "+", # 0xc3 + 196: "-", # 0xc4 + 197: "*", # 0xc5 + 198: "/", # 0xc6 + 199: "^", # 0xc7 + 200: " AND ", # 0xc8 + 201: " OR ", # 0xc9 + 202: ">", # 0xca + 203: "=", # 0xcb + 204: "<", # 0xcc + 205: " USING ", # 0xcd +} +# for k, v in sorted(BASIC_TOKENS.items()): +# print '%i: "%s", # %s' % (k, v.strip(), hex(k)) +# sys.exit() diff --git a/dragon32_CAS_decode.py b/dragon32_CAS_decode.py index 13355f04..b788d76f 100755 --- a/dragon32_CAS_decode.py +++ b/dragon32_CAS_decode.py @@ -4,14 +4,19 @@ Convert dragon 32 Cassetts WAV files into plain text. ===================================================== - In current state only the bits would be decoded, yet! + Currently ony supported: + * BASIC programs in tokenised form TODO: - detect even_odd startpoint! + - check BASIC programs in ASCII form: CSAVE "NAME",A + - detect even_odd startpoint! + - add cli + - write .BAS file - Interesting links: + Spec links: http://www.onastick.clara.net/cosio.htm http://www.cs.unc.edu/~yakowenk/coco/text/tapeformat.html + http://dragon32.info/info/basicfmt.html Many thanks to the people from: http://www.python-forum.de/viewtopic.php?f=1&t=32102 (de) @@ -26,6 +31,11 @@ import wave import sys import struct +import time + +# own modules +from utils import ProcessInfo, human_duration +from basic_tokens import BASIC_TOKENS ONE_HZ = 2400 # "1" is a single cycle at 2400 Hz @@ -49,86 +59,6 @@ DISPLAY_BLOCK_COUNT = 8 # How many bit block should be printet in one line? -BASIC_TOKENS = { - 128: " FOR ", # 0x80 - 129: " GO ", # 0x81 - 130: " REM ", # 0x82 - 131: "'", # 0x83 - 132: " ELSE ", # 0x84 - 133: " IF ", # 0x85 - 134: " DATA ", # 0x86 - 135: " PRINT ", # 0x87 - 136: " ON ", # 0x88 - 137: " INPUT ", # 0x89 - 138: " END ", # 0x8a - 139: " NEXT ", # 0x8b - 140: " DIM ", # 0x8c - 141: " READ ", # 0x8d - 142: " LET ", # 0x8e - 143: " RUN ", # 0x8f - 144: " RESTORE ", # 0x90 - 145: " RETURN ", # 0x91 - 146: " STOP ", # 0x92 - 147: " POKE ", # 0x93 - 148: " CONT ", # 0x94 - 149: " LIST ", # 0x95 - 150: " CLEAR ", # 0x96 - 151: " NEW ", # 0x97 - 152: " DEF ", # 0x98 - 153: " CLOAD ", # 0x99 - 154: " CSAVE ", # 0x9a - 155: " OPEN ", # 0x9b - 156: " CLOSE ", # 0x9c - 157: " LLIST ", # 0x9d - 158: " SET ", # 0x9e - 159: " RESET ", # 0x9f - 160: " CLS ", # 0xa0 - 161: " MOTOR ", # 0xa1 - 162: " SOUND ", # 0xa2 - 163: " AUDIO ", # 0xa3 - 164: " EXEC ", # 0xa4 - 165: " SKIPF ", # 0xa5 - 166: " DELETE ", # 0xa6 - 167: " EDIT ", # 0xa7 - 168: " TRON ", # 0xa8 - 169: " TROFF ", # 0xa9 - 170: " LINE ", # 0xaa - 171: " PCLS ", # 0xab - 172: " PSET ", # 0xac - 173: " PRESET ", # 0xad - 174: " SCREEN ", # 0xae - 175: " PCLEAR ", # 0xaf - 176: " COLOR ", # 0xb0 - 177: " CIRCLE ", # 0xb1 - 178: " PAINT ", # 0xb2 - 179: " GET ", # 0xb3 - 180: " PUT ", # 0xb4 - 181: " DRAW ", # 0xb5 - 182: " PCOPY ", # 0xb6 - 183: " PMODE ", # 0xb7 - 184: " PLAY ", # 0xb8 - 185: " DLOAD ", # 0xb9 - 186: " RENUM ", # 0xba - 187: " TAB(", # 0xbb - 188: " TO ", # 0xbc - 189: " SUB ", # 0xbd - 190: " FN ", # 0xbe - 191: " THEN ", # 0xbf - 192: " NOT ", # 0xc0 - 193: " STEP ", # 0xc1 - 194: " OFF ", # 0xc2 - 195: "+", # 0xc3 - 196: "-", # 0xc4 - 197: "*", # 0xc5 - 198: "/", # 0xc6 - 199: "^", # 0xc7 - 200: " AND ", # 0xc8 - 201: " OR ", # 0xc9 - 202: ">", # 0xca - 203: "=", # 0xcb - 204: "<", # 0xcc - 205: " USING ", # 0xcd -} def iter_steps(g, steps): """ @@ -234,20 +164,34 @@ def iter_wave_values(wavefile): yield frame_no, frame + + def iter_bits(wavefile, even_odd): framerate = wavefile.getframerate() # frames / second print "Framerate:", framerate + frame_count = wavefile.getnframes() + print "Numer of audio frames:", frame_count in_positive = even_odd in_negative = not even_odd toggle_count = 0 # Counter for detect a complete cycle previous_frame_no = 0 + process_info = ProcessInfo(frame_count, use_last_rates=4) + start_time = time.time() + next_status = start_time + 0.25 + + def _print_status(frame_no, framerate): + ms = float(frame_no) / framerate + rest, eta, rate = process_info.update(frame_no) + sys.stdout.write( + "\r%i frames readed. Position in WAV: %s - eta: %s (rate: %iFrames/sec)" % ( + frame_no, human_duration(ms), eta, rate + ) + ) + window_values = collections.deque(maxlen=MIN_TOGGLE_COUNT) for frame_no, value in iter_wave_values(wavefile): - # ms=float(frame_no)/framerate - # print "%i %0.5fms %i" % (frame_no, ms, value) - window_values.append(value) if len(window_values) >= MIN_TOGGLE_COUNT: positive_count, negative_count = count_sign(window_values) @@ -282,6 +226,13 @@ def iter_bits(wavefile, even_odd): # print "***", bit, hz, "Hz", "%0.5fms" % ms previous_frame_no = frame_no + if time.time() > next_status: + next_status = time.time() + 1 + _print_status(frame_no, framerate) + + _print_status(frame_no, framerate) + print + def count_continuous_pattern(bits, pattern): """ @@ -440,6 +391,8 @@ def get_block_info(bit_list): leader_count = count_continuous_pattern(bit_list, LEADER_BYTE) print "Found %i leader bytes" % leader_count + if leader_count == 0: + print "WARNING: leader bytes not found! Maybe 'even_odd' bool wrong???" to_cut = leader_count * 8 bit_list = bit_list[to_cut:] @@ -516,12 +469,15 @@ def add_data_block(self, block_length, block_bit_list): self.print_code_lines() def print_code_lines(self): + print "*"*79 for code_line in self.code_lines: # print repr(code_line) - print "pre bytes: %-10s code: %i %s" % ( - repr(code_line.pre_bytes), code_line.line_no, code_line.code - ) - + print "%i %s" % (code_line.line_no, code_line.code) +# print "pre bytes: %-10s code: %i %s" % ( +# repr(code_line.pre_bytes), code_line.line_no, code_line.code +# ) + print "*"*79 + class CassetteFile(object): """ @@ -546,7 +502,7 @@ class CassetteFile(object): of a binary file. """ def __init__(self, file_block_bit_list): - print_block_bit_list(block_bit_list) +# print_block_bit_list(block_bit_list) print_block_table(block_bit_list) self.data = block2bytes(block_bit_list) @@ -599,7 +555,6 @@ def add_block(self, block_type, block_length, block_bit_list): # FILENAME = "HelloWorld1 origin.wav" # even_odd = True - """ The origin BASIC code of the two WAV file is: @@ -612,12 +567,23 @@ def add_block(self, block_type, block_length, block_bit_list): https://github.com/jedie/python-code-snippets/raw/master/CodeSnippets/Dragon%2032/HelloWorld1%20xroar.wav """ + + # Test files from: + # http://archive.worldofdragon.org/archive/index.php?dir=Tapes/Dragon/wav/ +# FILENAME = "Quickbeam Software - Duplicas v3.0.wav" # binary! +# even_odd = False + +# FILENAME = "Dragon Data Ltd - Examples from the Manual - 39~58 [run].wav" +# even_odd = False + +# FILENAME = "1_MANIA.WAV" +# FILENAME = "2_DBJ.WAV" # TODO +# even_odd = False + + print "Read '%s'..." % FILENAME wavefile = wave.open(FILENAME, "r") - frame_count = wavefile.getnframes() - print "Numer of audio frames:", frame_count - print "read..." bit_list = list(iter_bits(wavefile, even_odd)) print "%i bits decoded." % len(bit_list) @@ -634,20 +600,33 @@ def add_block(self, block_type, block_length, block_bit_list): cassette = Cassette() while True: - print "="*79 + print "_"*79 bit_list, block_type, block_length = get_block_info(bit_list) - print "*** block type: 0x%x (%s)" % (block_type, BLOCK_TYPE_DICT[block_type]) + try: + block_type_name = BLOCK_TYPE_DICT[block_type] + except KeyError: + print "ERROR: Block type %s unknown in BLOCK_TYPE_DICT!" % hex(block_type) + print "Maybe 'even_odd' bool wrong???" + print "-"*79 + print "Debug bitlist:" + print_bitlist(bit_list) + print "-"*79 + sys.exit(-1) + + + print "*** block type: 0x%x (%s)" % (block_type, block_type_name) print "*** block length:", block_length if block_type == EOF_BLOCK: - print "end of file." + print "EOF-Block found" break bit_list, block_bit_list = pop_bytes_from_bit_list(bit_list, count=block_length) - cassette.add_block(block_type, block_length, block_bit_list) + print_block_table(block_bit_list) + # print_block_bit_list(block_bit_list) - print_block_bit_list(block_bit_list) + cassette.add_block(block_type, block_length, block_bit_list) print "="*79 diff --git a/utils.py b/utils.py new file mode 100644 index 00000000..b1e8cd4d --- /dev/null +++ b/utils.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python2 + +""" + :copyleft: 2013 by Jens Diemer + :license: GNU GPL v3 or above, see LICENSE for more details. +""" + + +import time + + +def human_duration(t): + """ + Converts a time duration into a friendly text representation. + + >>> human_duration("type error") + Traceback (most recent call last): + ... + TypeError: human_duration() argument must be integer or float + + >>> human_duration(0.01) + u'10.0 ms' + >>> human_duration(0.9) + u'900.0 ms' + >>> human_duration(65.5) + u'1.1 min' + >>> human_duration((60 * 60)-1) + u'59.0 min' + >>> human_duration(60*60) + u'1.0 hours' + >>> human_duration(1.05*60*60) + u'1.1 hours' + >>> human_duration(2.54 * 60 * 60 * 24 * 365) + u'2.5 years' + """ + if not isinstance(t, (int, float)): + raise TypeError("human_duration() argument must be integer or float") + + chunks = ( + (60 * 60 * 24 * 365, u'years'), + (60 * 60 * 24 * 30, u'months'), + (60 * 60 * 24 * 7, u'weeks'), + (60 * 60 * 24, u'days'), + (60 * 60, u'hours'), + ) + + if t < 1: + return u"%.1f ms" % round(t * 1000, 1) + if t < 60: + return u"%.1f sec" % round(t, 1) + if t < 60 * 60: + return u"%.1f min" % round(t / 60, 1) + + for seconds, name in chunks: + count = t / seconds + if count >= 1: + count = round(count, 1) + break + return u"%(number).1f %(type)s" % {'number': count, 'type': name} + + +class ProcessInfo(object): + """ + >>> p = ProcessInfo(100) + >>> p.update(1)[0] + 99 + >>> p = ProcessInfo(100) + >>> p.update(0) + (100, u'-', 0.0) + """ + def __init__(self, total, use_last_rates=4): + self.total = total + self.use_last_rates = use_last_rates + self.last_count = 0 + self.last_update = self.start_time = time.time() + self.rate_info = [] + + def update(self, count): + current_duration = time.time() - self.last_update + current_rate = float(count) / current_duration + self.rate_info.append(current_rate) + self.rate_info = self.rate_info[-self.use_last_rates:] + smoothed_rate = sum(self.rate_info) / len(self.rate_info) + rest = self.total - count + try: + eta = rest / smoothed_rate + except ZeroDivisionError: + # e.g. called before a "count+=1" + return self.total, u"-", 0.0 + human_eta = human_duration(eta) + return rest, human_eta, smoothed_rate + +if __name__ == "__main__": + import doctest + print doctest.testmod() From ecd0cfa37cadace269bbfd6f5815943a36a37799 Mon Sep 17 00:00:00 2001 From: Jens Diemer Date: Sun, 18 Aug 2013 12:05:15 +0200 Subject: [PATCH 010/151] handle 'function tokens' --- basic_tokens.py | 212 +++++++++++++++++++++++++---------------- dragon32_CAS_decode.py | 83 +++++++++++++--- 2 files changed, 199 insertions(+), 96 deletions(-) diff --git a/basic_tokens.py b/basic_tokens.py index 93843554..030ca9b8 100644 --- a/basic_tokens.py +++ b/basic_tokens.py @@ -6,85 +6,135 @@ """ BASIC_TOKENS = { - 128: " FOR ", # 0x80 - 129: " GO ", # 0x81 - 130: " REM ", # 0x82 - 131: "'", # 0x83 - 132: " ELSE ", # 0x84 - 133: " IF ", # 0x85 - 134: " DATA ", # 0x86 - 135: " PRINT ", # 0x87 - 136: " ON ", # 0x88 - 137: " INPUT ", # 0x89 - 138: " END ", # 0x8a - 139: " NEXT ", # 0x8b - 140: " DIM ", # 0x8c - 141: " READ ", # 0x8d - 142: " LET ", # 0x8e - 143: " RUN ", # 0x8f - 144: " RESTORE ", # 0x90 - 145: " RETURN ", # 0x91 - 146: " STOP ", # 0x92 - 147: " POKE ", # 0x93 - 148: " CONT ", # 0x94 - 149: " LIST ", # 0x95 - 150: " CLEAR ", # 0x96 - 151: " NEW ", # 0x97 - 152: " DEF ", # 0x98 - 153: " CLOAD ", # 0x99 - 154: " CSAVE ", # 0x9a - 155: " OPEN ", # 0x9b - 156: " CLOSE ", # 0x9c - 157: " LLIST ", # 0x9d - 158: " SET ", # 0x9e - 159: " RESET ", # 0x9f - 160: " CLS ", # 0xa0 - 161: " MOTOR ", # 0xa1 - 162: " SOUND ", # 0xa2 - 163: " AUDIO ", # 0xa3 - 164: " EXEC ", # 0xa4 - 165: " SKIPF ", # 0xa5 - 166: " DELETE ", # 0xa6 - 167: " EDIT ", # 0xa7 - 168: " TRON ", # 0xa8 - 169: " TROFF ", # 0xa9 - 170: " LINE ", # 0xaa - 171: " PCLS ", # 0xab - 172: " PSET ", # 0xac - 173: " PRESET ", # 0xad - 174: " SCREEN ", # 0xae - 175: " PCLEAR ", # 0xaf - 176: " COLOR ", # 0xb0 - 177: " CIRCLE ", # 0xb1 - 178: " PAINT ", # 0xb2 - 179: " GET ", # 0xb3 - 180: " PUT ", # 0xb4 - 181: " DRAW ", # 0xb5 - 182: " PCOPY ", # 0xb6 - 183: " PMODE ", # 0xb7 - 184: " PLAY ", # 0xb8 - 185: " DLOAD ", # 0xb9 - 186: " RENUM ", # 0xba - 187: " TAB(", # 0xbb - 188: " TO ", # 0xbc - 189: " SUB ", # 0xbd - 190: " FN ", # 0xbe - 191: " THEN ", # 0xbf - 192: " NOT ", # 0xc0 - 193: " STEP ", # 0xc1 - 194: " OFF ", # 0xc2 - 195: "+", # 0xc3 - 196: "-", # 0xc4 - 197: "*", # 0xc5 - 198: "/", # 0xc6 - 199: "^", # 0xc7 - 200: " AND ", # 0xc8 - 201: " OR ", # 0xc9 - 202: ">", # 0xca - 203: "=", # 0xcb - 204: "<", # 0xcc - 205: " USING ", # 0xcd + 0x80: "FOR", + 0x81: "GO", + 0x82: "REM", + 0x83: "'", + 0x84: "ELSE", + 0x85: "IF", + 0x86: "DATA", + 0x87: "PRINT", + 0x88: "ON", + 0x89: "INPUT", + 0x8a: "END", + 0x8b: "NEXT", + 0x8c: "DIM", + 0x8d: "READ", + 0x8e: "LET", + 0x8f: "RUN", + 0x90: "RESTORE", + 0x91: "RETURN", + 0x92: "STOP", + 0x93: "POKE", + 0x94: "CONT", + 0x95: "LIST", + 0x96: "CLEAR", + 0x97: "NEW", + 0x98: "DEF", + 0x99: "CLOAD", + 0x9a: "CSAVE", + 0x9b: "OPEN", + 0x9c: "CLOSE", + 0x9d: "LLIST", + 0x9e: "SET", + 0x9f: "RESET", + 0xa0: "CLS", + 0xa1: "MOTOR", + 0xa2: "SOUND", + 0xa3: "AUDIO", + 0xa4: "EXEC", + 0xa5: "SKIPF", + 0xa6: "DELETE", + 0xa7: "EDIT", + 0xa8: "TRON", + 0xa9: "TROFF", + 0xaa: "LINE", + 0xab: "PCLS", + 0xac: "PSET", + 0xad: "PRESET", + 0xae: "SCREEN", + 0xaf: "PCLEAR", + 0xb0: "COLOR", + 0xb1: "CIRCLE", + 0xb2: "PAINT", + 0xb3: "GET", + 0xb4: "PUT", + 0xb5: "DRAW", + 0xb6: "PCOPY", + 0xb7: "PMODE", + 0xb8: "PLAY", + 0xb9: "DLOAD", + 0xba: "RENUM", + 0xbb: "TAB(", + 0xbc: "TO", + 0xbd: "SUB", + 0xbe: "FN", + 0xbf: "THEN", + 0xc0: "NOT", + 0xc1: "STEP", + 0xc2: "OFF", + 0xc3: "+", + 0xc4: "-", + 0xc5: "*", + 0xc6: "/", + 0xc7: "^", + 0xc8: "AND", + 0xc9: "OR", + 0xca: ">", + 0xcb: "=", + 0xcc: "<", + 0xcd: "USING", } -# for k, v in sorted(BASIC_TOKENS.items()): -# print '%i: "%s", # %s' % (k, v.strip(), hex(k)) -# sys.exit() + + +# Function tokens - all proceeded by 0xff to differentiate from operators +FUNCTION_TOKEN = { + 0x80: "SGN", + 0x81: "INT", + 0x82: "ABS", + 0x83: "POS", + 0x84: "RND", + 0x85: "SQR", + 0x86: "LOG", + 0x87: "EXP", + 0x88: "SIN", + 0x89: "COS", + 0x8a: "TAN", + 0x8b: "ATN", + 0x8c: "PEEK", + 0x8d: "LEN", + 0x8e: "STR$", + 0x8f: "VAL", + 0x90: "ASC", + 0x91: "CHR$", + 0x92: "EOF", + 0x93: "JOYSTK", + 0x94: "FIX", + 0x95: "HEX$", + 0x96: "LEFT$", + 0x97: "RIGHT$", + 0x98: "MID$", + 0x99: "POINT", + 0x9a: "INKEY$", + 0x9b: "MEM", + 0x9c: "VARPTR", + 0x9d: "INSTR", + 0x9e: "TIMER", + 0x9f: "PPOINT", + 0xa0: "STRING$", + 0xa1: "USR", +} + + +if __name__ == "__main__": + def pprint_formated(d): + for k, v in sorted(d.items()): + print ' %s: "%s",' % (hex(k), v.strip()) + + print "BASIC_TOKENS = {" + pprint_formated(BASIC_TOKENS) + print "}" + + print "FUNCTION_TOKEN = {" + pprint_formated(FUNCTION_TOKEN) + print "}" diff --git a/dragon32_CAS_decode.py b/dragon32_CAS_decode.py index b788d76f..32c4b005 100755 --- a/dragon32_CAS_decode.py +++ b/dragon32_CAS_decode.py @@ -35,7 +35,7 @@ # own modules from utils import ProcessInfo, human_duration -from basic_tokens import BASIC_TOKENS +from basic_tokens import BASIC_TOKENS, FUNCTION_TOKEN ONE_HZ = 2400 # "1" is a single cycle at 2400 Hz @@ -366,6 +366,18 @@ def bits2byte_no(bits): bits = list2str(bits) return int(bits, 2) +def byte_list2bit_list(data): + """ + >>> data = (0x0,0x1e,0x8b,0x20,0x49,0x0) + >>> byte_list2bit_list(data) + ['00000000', '01111000', '11010001', '00000100', '10010010', '00000000'] + """ + bit_list = [] + for char in data: + bits = '{0:08b}'.format(char) + bits = bits[::-1] + bit_list.append(bits) + return bit_list def block2bytes(block_bit_list): bytes = "".join([chr(bits2byte_no(block)) for block in block_bit_list]) @@ -380,6 +392,21 @@ def print_block_table(block_bit_list): list2str(block), hex(byte_no), byte_no, repr(character) ) +def print_as_hex(block_bit_list): + line="" + for block in block_bit_list: + byte_no = bits2byte_no(block) + character = chr(byte_no) + line += hex(byte_no) + print line + +def print_as_hex_list(block_bit_list): + line=[] + for block in block_bit_list: + byte_no = bits2byte_no(block) + character = chr(byte_no) + line.append(hex(byte_no)) + print ",".join(line) def get_block_info(bit_list): leader_pos = find_iter_window(bit_list, LEADER_BYTE) # Search for LEADER_BYTE in bit-by-bit steps @@ -430,7 +457,31 @@ def __init__(self): self.code_lines = [] def add_data_block(self, block_length, block_bit_list): + """ + >>> fc = FileContent() + >>> data = ( + ... 0x1e,0x12,0x0,0xa,0x80,0x20,0x49,0x20,0xcb,0x20,0x31,0x20,0xbc,0x20,0x31,0x30,0x0,0x1e,0x29,0x0,0x14,0x87,0x20,0x49,0x3b,0x22,0x48,0x45,0x4c,0x4c,0x4f,0x20,0x57,0x4f,0x52,0x4c,0x44,0x21,0x22,0x0,0x1e,0x31,0x0,0x1e,0x8b,0x20,0x49,0x0,0x0,0x0 + ... ) + >>> bit_list=byte_list2bit_list(data) + >>> fc.add_data_block(0, bit_list) + >>> fc.print_code_lines() + 10 FOR I = 1 TO 10 + 20 PRINT I;"HELLO WORLD!" + 30 NEXT I + + >>> fc = FileContent() + >>> data = ( + ... 0x1e,0x4a,0x0, + ... 0x1e,0x58,0xcb,0x58,0xc3,0x4c,0xc5,0xff,0x88,0x28,0x52,0x29,0x3a,0x59,0xcb,0x59,0xc3,0x4c,0xc5,0xff,0x89,0x28,0x52,0x29, + ... 0x0,0x0,0x0 + ... ) + >>> bit_list=byte_list2bit_list(data) + >>> fc.add_data_block(0, bit_list) + >>> fc.print_code_lines() + 30 X=X+L*SIN(R):Y=Y+L*COS(R) + """ in_code_line = False + func_token = False pre_bytes = [] line_no = None code_line = "" @@ -458,26 +509,24 @@ def add_data_block(self, block_length, block_bit_list): line_no = byte_no continue - if byte_no in BASIC_TOKENS: - character = BASIC_TOKENS[byte_no].strip() # XXX: strip direct in BASIC_TOKENS ??? + if byte_no == 0xff: # Next byte is a function token + func_token = True + continue + elif func_token == True: + func_token = False + character = FUNCTION_TOKEN[byte_no] + elif byte_no in BASIC_TOKENS: + character = BASIC_TOKENS[byte_no] else: character = chr(byte_no) code_line += character else: pre_bytes.append(byte_no) - self.print_code_lines() - def print_code_lines(self): - print "*"*79 for code_line in self.code_lines: -# print repr(code_line) print "%i %s" % (code_line.line_no, code_line.code) -# print "pre bytes: %-10s code: %i %s" % ( -# repr(code_line.pre_bytes), code_line.line_no, code_line.code -# ) - print "*"*79 - + class CassetteFile(object): """ @@ -511,7 +560,11 @@ def __init__(self, file_block_bit_list): self.file_content = FileContent() def add_data_block(self, block_length, block_bit_list): + print_as_hex_list(block_bit_list) self.file_content.add_data_block(block_length, block_bit_list) + print "*"*79 + self.file_content.print_code_lines() + print "*"*79 def __repr__(self): return "" % (self.filename, repr(self.data)) @@ -544,7 +597,7 @@ def add_block(self, block_type, block_length, block_bit_list): verbose=False # verbose=True ) -# sys.exit() + #~ sys.exit() # created by Xroar Emulator @@ -573,8 +626,8 @@ def add_block(self, block_type, block_length, block_bit_list): # FILENAME = "Quickbeam Software - Duplicas v3.0.wav" # binary! # even_odd = False -# FILENAME = "Dragon Data Ltd - Examples from the Manual - 39~58 [run].wav" -# even_odd = False + FILENAME = "Dragon Data Ltd - Examples from the Manual - 39~58 [run].wav" + even_odd = False # FILENAME = "1_MANIA.WAV" # FILENAME = "2_DBJ.WAV" # TODO From 82f526b1c85d8d22ad7d918de7713c37525a2f01 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Sun, 18 Aug 2013 18:05:48 +0200 Subject: [PATCH 011/151] Speedup by reading WAVE in blocks --- dragon32_CAS_decode.py | 43 ++++++++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/dragon32_CAS_decode.py b/dragon32_CAS_decode.py index 32c4b005..2cb5904c 100755 --- a/dragon32_CAS_decode.py +++ b/dragon32_CAS_decode.py @@ -55,6 +55,7 @@ EOF_BLOCK: "end-of-file block", } +WAVE_READ_SIZE = 1024 # How many frames should be read from WAVE file at once? MIN_TOGGLE_COUNT = 3 # How many samples must be in pos/neg to count a cycle? DISPLAY_BLOCK_COUNT = 8 # How many bit block should be printet in one line? @@ -154,14 +155,16 @@ def iter_wave_values(wavefile): raise NotImplementedError("Only sample width 2 or 1 are supported, yet!") print "struct_unpack_str:", struct_unpack_str - for frame_no in xrange(frame_count): - frame = wavefile.readframes(1) - if not frame: + frame_no = 0 + while True: + frames = wavefile.readframes(WAVE_READ_SIZE) + if not frames: break - frame = struct.unpack(struct_unpack_str, frame)[0] - - yield frame_no, frame + for frame in frames: + frame_no += 1 + frame = struct.unpack(struct_unpack_str, frame)[0] + yield frame_no, frame @@ -176,6 +179,7 @@ def iter_bits(wavefile, even_odd): in_negative = not even_odd toggle_count = 0 # Counter for detect a complete cycle previous_frame_no = 0 + bit_count = 0 process_info = ProcessInfo(frame_count, use_last_rates=4) start_time = time.time() @@ -218,8 +222,10 @@ def _print_status(frame_no, framerate): dst_one = abs(ONE_HZ - hz) dst_nul = abs(NUL_HZ - hz) if dst_one < dst_nul: + bit_count += 1 yield 1 else: + bit_count += 1 yield 0 # ms=float(frame_no)/framerate @@ -232,6 +238,11 @@ def _print_status(frame_no, framerate): _print_status(frame_no, framerate) print + duration = time.time() - start_time + rate = bit_count / duration / 8 / 1024 + print "%i bits decoded in %s (%.1fKBytes/s)" % (bit_count, human_duration(duration), rate) + print + print def count_continuous_pattern(bits, pattern): @@ -392,6 +403,7 @@ def print_block_table(block_bit_list): list2str(block), hex(byte_no), byte_no, repr(character) ) + def print_as_hex(block_bit_list): line="" for block in block_bit_list: @@ -400,6 +412,7 @@ def print_as_hex(block_bit_list): line += hex(byte_no) print line + def print_as_hex_list(block_bit_list): line=[] for block in block_bit_list: @@ -408,6 +421,7 @@ def print_as_hex_list(block_bit_list): line.append(hex(byte_no)) print ",".join(line) + def get_block_info(bit_list): leader_pos = find_iter_window(bit_list, LEADER_BYTE) # Search for LEADER_BYTE in bit-by-bit steps print "Start leader '%s' found at position: %i" % (LEADER_BYTE, leader_pos) @@ -552,7 +566,7 @@ class CassetteFile(object): """ def __init__(self, file_block_bit_list): # print_block_bit_list(block_bit_list) - print_block_table(block_bit_list) +# print_block_table(block_bit_list) self.data = block2bytes(block_bit_list) self.filename = self.data[:8] @@ -601,11 +615,11 @@ def add_block(self, block_type, block_length, block_bit_list): # created by Xroar Emulator - FILENAME = "HelloWorld1 xroar.wav" - even_odd = False +# FILENAME = "HelloWorld1 xroar.wav" +# even_odd = False # created by origin Dragon 32 machine -# FILENAME = "HelloWorld1 origin.wav" +# FILENAME = "HelloWorld1 origin.wav" # 109922 frames, 2735 bits (raw) # even_odd = True """ @@ -629,7 +643,7 @@ def add_block(self, block_type, block_length, block_bit_list): FILENAME = "Dragon Data Ltd - Examples from the Manual - 39~58 [run].wav" even_odd = False -# FILENAME = "1_MANIA.WAV" +# FILENAME = "1_MANIA.WAV" # 148579 frames, 4879 bits (raw) # FILENAME = "2_DBJ.WAV" # TODO # even_odd = False @@ -639,9 +653,6 @@ def add_block(self, block_type, block_length, block_bit_list): print "read..." bit_list = list(iter_bits(wavefile, even_odd)) - print "%i bits decoded." % len(bit_list) - print - # print "-"*79 # print_bitlist(bit_list) @@ -676,8 +687,8 @@ def add_block(self, block_type, block_length, block_bit_list): bit_list, block_bit_list = pop_bytes_from_bit_list(bit_list, count=block_length) - print_block_table(block_bit_list) - # print_block_bit_list(block_bit_list) +# print_block_table(block_bit_list) +# print_block_bit_list(block_bit_list) cassette.add_block(block_type, block_length, block_bit_list) print "="*79 From 7504eee1d64a00ca9fea1dddccd65761f1933ed5 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Sun, 18 Aug 2013 18:48:56 +0200 Subject: [PATCH 012/151] bugfix in unpack wav frames --- dragon32_CAS_decode.py | 47 ++++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/dragon32_CAS_decode.py b/dragon32_CAS_decode.py index 2cb5904c..485fb4a0 100755 --- a/dragon32_CAS_decode.py +++ b/dragon32_CAS_decode.py @@ -55,7 +55,11 @@ EOF_BLOCK: "end-of-file block", } -WAVE_READ_SIZE = 1024 # How many frames should be read from WAVE file at once? +WAVE_READ_SIZE = 16 * 1024 # How many frames should be read from WAVE file at once? +WAV_UNPACK_STR = { + 1: "<%db", # 8-bit wave + 2: "<%dh", # 16-bit wave +} MIN_TOGGLE_COUNT = 3 # How many samples must be in pos/neg to count a cycle? DISPLAY_BLOCK_COUNT = 8 # How many bit block should be printet in one line? @@ -137,23 +141,26 @@ def count_sign(values): def iter_wave_values(wavefile): - samplewidth = wavefile.getsampwidth() # i.e 1 for 8-bit samples, 2 for 16-bit samples - print "samplewidth:", samplewidth + """ + generator that yield integers for WAVE files. + + returned sample values are in this ranges: + 8-bit: -255..255 + 16-bit: -32768..32768 + """ nchannels = wavefile.getnchannels() # typically 1 for mono, 2 for stereo print "channels:", nchannels - assert nchannels == 1, "Only MONO files are supported, yet!" - frame_count = wavefile.getnframes() # number of audio frames + samplewidth = wavefile.getsampwidth() # 1 for 8-bit, 2 for 16-bit, 4 for 32-bit samples + print "samplewidth:", samplewidth - # FIXME - if samplewidth == 1: - struct_unpack_str = "b" - elif samplewidth == 2: - struct_unpack_str = " Date: Sun, 18 Aug 2013 19:06:08 +0200 Subject: [PATCH 013/151] support 32Bit wave files, too. Why? Don't know... ;) --- dragon32_CAS_decode.py | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/dragon32_CAS_decode.py b/dragon32_CAS_decode.py index 485fb4a0..68060577 100755 --- a/dragon32_CAS_decode.py +++ b/dragon32_CAS_decode.py @@ -57,8 +57,9 @@ WAVE_READ_SIZE = 16 * 1024 # How many frames should be read from WAVE file at once? WAV_UNPACK_STR = { - 1: "<%db", # 8-bit wave - 2: "<%dh", # 16-bit wave + 1: "<%db", # 8-bit wave file + 2: "<%dh", # 16-bit wave file + 4: "<%dl", # 32-bit wave file TODO: Test it } MIN_TOGGLE_COUNT = 3 # How many samples must be in pos/neg to count a cycle? @@ -145,21 +146,22 @@ def iter_wave_values(wavefile): generator that yield integers for WAVE files. returned sample values are in this ranges: - 8-bit: -255..255 - 16-bit: -32768..32768 + 8-bit: -255..255 + 16-bit: -32768..32768 + 32-bit: -2147483648..2147483647 """ nchannels = wavefile.getnchannels() # typically 1 for mono, 2 for stereo print "channels:", nchannels 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 - print "samplewidth:", samplewidth + print "samplewidth: %i (%sBit wave file)" % (samplewidth, samplewidth * 8) try: struct_unpack_str = WAV_UNPACK_STR[samplewidth] except KeyError: raise NotImplementedError( - "Only sample width %s are supported, yet!" % ",".join([str(i) for i in WAV_UNPACK_STR.keys()]) + "Only %s wave files are supported, yet!" % ", ".join(["%sBit" % (i * 8) for i in WAV_UNPACK_STR.keys()]) ) frame_no = 0 @@ -179,7 +181,7 @@ def iter_bits(wavefile, even_odd): framerate = wavefile.getframerate() # frames / second print "Framerate:", framerate frame_count = wavefile.getnframes() - print "Numer of audio frames:", frame_count + print "Number of audio frames:", frame_count in_positive = even_odd in_negative = not even_odd @@ -246,7 +248,9 @@ def _print_status(frame_no, framerate): print duration = time.time() - start_time rate = bit_count / duration / 8 / 1024 - print "%i bits decoded in %s (%.1fKBytes/s)" % (bit_count, human_duration(duration), rate) + print "%i bits decoded from %i audio samples in %s (%.1fKBytes/s)" % ( + bit_count, frame_no, human_duration(duration), rate + ) print print @@ -647,12 +651,12 @@ def add_block(self, block_type, block_length, block_bit_list): # FILENAME = "Quickbeam Software - Duplicas v3.0.wav" # binary! # even_odd = False - FILENAME = "Dragon Data Ltd - Examples from the Manual - 39~58 [run].wav" - even_odd = False +# FILENAME = "Dragon Data Ltd - Examples from the Manual - 39~58 [run].wav" +# even_odd = False # FILENAME = "1_MANIA.WAV" # 148579 frames, 4879 bits (raw) -# FILENAME = "2_DBJ.WAV" # TODO -# even_odd = False + FILENAME = "2_DBJ.WAV" # TODO + even_odd = False print "Read '%s'..." % FILENAME From f4563e1a4f54139b806b8f61367be5436c00e2fc Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Sun, 18 Aug 2013 19:39:56 +0200 Subject: [PATCH 014/151] refactor reading --- dragon32_CAS_decode.py | 46 +++++++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/dragon32_CAS_decode.py b/dragon32_CAS_decode.py index 68060577..a9a96ec0 100755 --- a/dragon32_CAS_decode.py +++ b/dragon32_CAS_decode.py @@ -151,11 +151,8 @@ def iter_wave_values(wavefile): 32-bit: -2147483648..2147483647 """ nchannels = wavefile.getnchannels() # typically 1 for mono, 2 for stereo - print "channels:", nchannels 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 - print "samplewidth: %i (%sBit wave file)" % (samplewidth, samplewidth * 8) try: struct_unpack_str = WAV_UNPACK_STR[samplewidth] @@ -177,12 +174,7 @@ def iter_wave_values(wavefile): yield frame_no, frame -def iter_bits(wavefile, even_odd): - framerate = wavefile.getframerate() # frames / second - print "Framerate:", framerate - frame_count = wavefile.getnframes() - print "Number of audio frames:", frame_count - +def samples2bits(samples, framerate, frame_count, even_odd): in_positive = even_odd in_negative = not even_odd toggle_count = 0 # Counter for detect a complete cycle @@ -203,7 +195,7 @@ def _print_status(frame_no, framerate): ) window_values = collections.deque(maxlen=MIN_TOGGLE_COUNT) - for frame_no, value in iter_wave_values(wavefile): + for frame_no, value in samples: window_values.append(value) if len(window_values) >= MIN_TOGGLE_COUNT: positive_count, negative_count = count_sign(window_values) @@ -387,6 +379,7 @@ def bits2byte_no(bits): bits = list2str(bits) return int(bits, 2) + def byte_list2bit_list(data): """ >>> data = (0x0,0x1e,0x8b,0x20,0x49,0x0) @@ -582,6 +575,12 @@ def __init__(self, file_block_bit_list): self.data = block2bytes(block_bit_list) self.filename = self.data[:8] + self.file_type = self.data[8] + print "file type:", repr(self.file_type), +# if self.file_type == 0x00: +# print "BASIC programm" +# if self.file_type == 0x00: + self.file_content = FileContent() def add_data_block(self, block_length, block_bit_list): @@ -626,8 +625,8 @@ def add_block(self, block_type, block_length, block_bit_list): # created by Xroar Emulator -# FILENAME = "HelloWorld1 xroar.wav" -# even_odd = False + FILENAME = "HelloWorld1 xroar.wav" + even_odd = False # created by origin Dragon 32 machine # FILENAME = "HelloWorld1 origin.wav" # 109922 frames, 2735 bits (raw) @@ -655,15 +654,30 @@ def add_block(self, block_type, block_length, block_bit_list): # even_odd = False # FILENAME = "1_MANIA.WAV" # 148579 frames, 4879 bits (raw) - FILENAME = "2_DBJ.WAV" # TODO - even_odd = False +# FILENAME = "2_DBJ.WAV" # TODO +# even_odd = False print "Read '%s'..." % FILENAME wavefile = wave.open(FILENAME, "r") - print "read..." - bit_list = list(iter_bits(wavefile, even_odd)) + framerate = wavefile.getframerate() # frames / second + print "Framerate:", framerate + frame_count = wavefile.getnframes() + print "Number of audio frames:", frame_count + nchannels = wavefile.getnchannels() # typically 1 for mono, 2 for stereo + print "channels:", nchannels + 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 + print "samplewidth: %i (%sBit wave file)" % (samplewidth, samplewidth * 8) + + start_time = time.time() + print "Read wave file...", + wave_samples = list(iter_wave_values(wavefile)) + print "DONE in %s" % human_duration(time.time() - start_time) + + print "Convert WAVE samples to binary data..." + bit_list = list(samples2bits(wave_samples, framerate, frame_count, even_odd)) # print "-"*79 # print_bitlist(bit_list) From 4a20530155623504752102c11ba76116eff27152 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Mon, 19 Aug 2013 14:59:48 +0200 Subject: [PATCH 015/151] Use MAX_HZ_VARIATION to seperate bit 1 or bit 0 display some statistics of bit detection --- dragon32_CAS_decode.py | 306 +++++++++++++++++++++++++++++++---------- utils.py | 17 +++ 2 files changed, 249 insertions(+), 74 deletions(-) diff --git a/dragon32_CAS_decode.py b/dragon32_CAS_decode.py index a9a96ec0..1dc3923f 100755 --- a/dragon32_CAS_decode.py +++ b/dragon32_CAS_decode.py @@ -32,14 +32,23 @@ import sys import struct import time +import array + +try: + import audioop +except ImportError, err: + # e.g. PyPy, see: http://bugs.pypy.org/msg4430 + print "Can't use audioop:", err + audioop = None # own modules -from utils import ProcessInfo, human_duration +from utils import ProcessInfo, human_duration, average from basic_tokens import BASIC_TOKENS, FUNCTION_TOKEN -ONE_HZ = 2400 # "1" is a single cycle at 2400 Hz -NUL_HZ = 1200 # "0" is a single cycle at 1200 Hz +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 ? LEADER_BYTE = "10101010" # 0x55 SYNC_BYTE = "00111100" # 0x3C @@ -55,13 +64,19 @@ EOF_BLOCK: "end-of-file block", } +# WAVE_RESAMPLE = 22050 # Downsample wave file to this sample rate +# WAVE_RESAMPLE = 11025 # Downsample wave file to this sample rate +# WAVE_RESAMPLE = 8000 # Downsample wave file to this sample rate + +WAVE_RESAMPLE = None # Don't change sample rate + 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 } -MIN_TOGGLE_COUNT = 3 # How many samples must be in pos/neg to count a cycle? +MIN_TOGGLE_COUNT = 4 # How many samples must be in pos/neg to count a cycle? DISPLAY_BLOCK_COUNT = 8 # How many bit block should be printet in one line? @@ -124,23 +139,6 @@ def iter_window(g, window_size): yield list(values) -def count_sign(values): - """ - >>> count_sign([3,-1,-2]) - (1, 2) - >>> count_sign([0,-1]) - (0, 1) - """ - positive_count = 0 - negative_count = 0 - for value in values: - if value > 0: - positive_count += 1 - elif value < 0: - negative_count += 1 - return positive_count, negative_count - - def iter_wave_values(wavefile): """ generator that yield integers for WAVE files. @@ -161,22 +159,82 @@ def iter_wave_values(wavefile): "Only %s wave files are supported, yet!" % ", ".join(["%sBit" % (i * 8) for i in WAV_UNPACK_STR.keys()]) ) + def _print_status(frame_no, framerate): + ms = float(frame_no) / framerate + rest, eta, rate = process_info.update(frame_no) + sys.stdout.write( + "\r%i frames (wav pos:%s) eta: %s (rate: %iFrames/sec) " % ( + frame_no, human_duration(ms), eta, rate + ) + ) + + framerate = wavefile.getframerate() # frames / second + frame_count = wavefile.getnframes() + + process_info = ProcessInfo(frame_count, use_last_rates=4) + start_time = time.time() + next_status = start_time + 0.25 + + new_rate = None + if audioop is not None and WAVE_RESAMPLE is not None and framerate > WAVE_RESAMPLE: + new_rate = WAVE_RESAMPLE + print "resample from %iHz/sec. to %sHz/sec" % (framerate, new_rate) + framerate = WAVE_RESAMPLE + frame_no = 0 + ratecv_state = None while True: frames = wavefile.readframes(WAVE_READ_SIZE) if not frames: break + if new_rate is not None: + # downsample the wave file + # FIXME! See: http://www.python-forum.de/viewtopic.php?f=11&t=6118&p=244377#p244377 + print "before:", len(frames), new_rate + frames, ratecv_state = audioop.ratecv( + frames, samplewidth, nchannels, framerate, new_rate, ratecv_state, 1, 1 + ) + print "after:", len(frames), new_rate + + if time.time() > next_status: + next_status = time.time() + 1 + _print_status(frame_no, framerate) + 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 + _print_status(frame_no, framerate) + print + + +MIN_SAMPLE_VALUE = 5 +def count_sign(values, min_value): + """ + >>> count_sign([3,-1,-2], 0) + (1, 2) + >>> count_sign([3,-1,-2], 2) + (1, 0) + >>> count_sign([0,-1],0) + (0, 1) + """ + positive_count = 0 + negative_count = 0 + for value in values: + if value > min_value: + positive_count += 1 + elif value < -min_value: + negative_count += 1 + return positive_count, negative_count + def samples2bits(samples, framerate, frame_count, even_odd): in_positive = even_odd in_negative = not even_odd + toggle_count = 0 # Counter for detect a complete cycle previous_frame_no = 0 bit_count = 0 @@ -189,52 +247,94 @@ def _print_status(frame_no, framerate): ms = float(frame_no) / framerate rest, eta, rate = process_info.update(frame_no) sys.stdout.write( - "\r%i frames readed. Position in WAV: %s - eta: %s (rate: %iFrames/sec)" % ( + "\r%i frames (wav pos:%s) eta: %s (rate: %iFrames/sec) " % ( frame_no, human_duration(ms), eta, rate ) ) window_values = collections.deque(maxlen=MIN_TOGGLE_COUNT) - for frame_no, value in samples: + + # Fill window deque + for frame_no, value in samples[:MIN_TOGGLE_COUNT]: window_values.append(value) - if len(window_values) >= MIN_TOGGLE_COUNT: - positive_count, negative_count = count_sign(window_values) - - # print window_values, positive_count, negative_count - if not in_positive and positive_count == MIN_TOGGLE_COUNT and negative_count == 0: - # go into a positive sinus area - in_positive = True - in_negative = False - toggle_count += 1 - elif not in_negative and negative_count == MIN_TOGGLE_COUNT and positive_count == 0: - # go into a negative sinus area - in_negative = True - in_positive = False - toggle_count += 1 - - if toggle_count >= 2: - # a single sinus cycle complete - toggle_count = 0 - - frame_count = frame_no - previous_frame_no - hz = framerate / frame_count - - dst_one = abs(ONE_HZ - hz) - dst_nul = abs(NUL_HZ - hz) - if dst_one < dst_nul: - bit_count += 1 - yield 1 - else: - bit_count += 1 - yield 0 - # ms=float(frame_no)/framerate - # print "***", bit, hz, "Hz", "%0.5fms" % ms - previous_frame_no = frame_no + bit_one_min_hz = BIT_ONE_HZ - MAX_HZ_VARIATION + bit_one_max_hz = BIT_ONE_HZ + MAX_HZ_VARIATION + + bit_nul_min_hz = BIT_NUL_HZ - MAX_HZ_VARIATION + bit_nul_max_hz = BIT_NUL_HZ + MAX_HZ_VARIATION - if time.time() > next_status: - next_status = time.time() + 1 - _print_status(frame_no, framerate) + one_hz_count = 0 + one_hz_min = sys.maxint + one_hz_avg = None + one_hz_max = 0 + nul_hz_count = 0 + nul_hz_min = sys.maxint + nul_hz_avg = None + nul_hz_max = 0 + + old_status = (-1, -1) + for frame_no, value in samples[MIN_TOGGLE_COUNT:]: + window_values.append(value) + + new_status = count_sign(window_values, MIN_SAMPLE_VALUE) + if new_status == old_status: + # ignore the frame if status not changed +# print new_status, "skip" + continue + positive_count, negative_count = old_status = new_status + + # print window_values, positive_count, negative_count + if not in_positive and positive_count == MIN_TOGGLE_COUNT and negative_count == 0: + # go into a positive sinus area + in_positive = True + in_negative = False + toggle_count += 1 + elif not in_negative and negative_count == MIN_TOGGLE_COUNT and positive_count == 0: + # go into a negative sinus area + in_negative = True + in_positive = False + toggle_count += 1 + else: +# print "wrong:", positive_count, negative_count + continue + + if toggle_count >= 2: + # a single sinus cycle complete + toggle_count = 0 + + frame_count = frame_no - previous_frame_no + previous_frame_no = frame_no + hz = framerate / frame_count +# print "%sHz" % hz + + if hz > bit_one_min_hz and hz < bit_one_max_hz: +# print "bit 1" + bit_count += 1 + yield 1 + one_hz_count += 1 + if hz < one_hz_min: + one_hz_min = hz + if hz > one_hz_max: + one_hz_max = hz + one_hz_avg = average(one_hz_avg, hz, one_hz_count) + elif hz > bit_nul_min_hz and hz < bit_nul_max_hz: +# print "bit 0" + bit_count += 1 + yield 0 + nul_hz_count += 1 + if hz < nul_hz_min: + nul_hz_min = hz + if hz > nul_hz_max: + nul_hz_max = hz + nul_hz_avg = average(nul_hz_avg, hz, nul_hz_count) + else: + print "Skip signal with %sHz." % hz + continue + + if time.time() > next_status: + next_status = time.time() + 1 + _print_status(frame_no, framerate) _print_status(frame_no, framerate) print @@ -244,7 +344,12 @@ def _print_status(frame_no, framerate): bit_count, frame_no, human_duration(duration), rate ) print - print + print "Bit 1: %s-%sHz avg: %.1fHz variation: %sHz" % ( + one_hz_min, one_hz_max, one_hz_avg, one_hz_max - one_hz_min + ) + print "Bit 0: %s-%sHz avg: %.1fHz variation: %sHz" % ( + nul_hz_min, nul_hz_max, nul_hz_avg, nul_hz_max - nul_hz_min + ) def count_continuous_pattern(bits, pattern): @@ -536,6 +641,7 @@ def add_data_block(self, block_length, block_bit_list): character = BASIC_TOKENS[byte_no] else: character = chr(byte_no) + # print byte_no, repr(character) code_line += character else: pre_bytes.append(byte_no) @@ -612,6 +718,23 @@ def add_block(self, block_type, block_length, block_bit_list): raise TypeError("Block type %s unkown!" & hex(block_type)) +def print_bit_list_stats(bit_list): + """ + >>> print_bit_list_stats([1,1,1,1,0,0,0,0]) + 8 Bits: 4 positive bits and 4 negative bits + """ + print "%i Bits:" % len(bit_list), + positive_count = 0 + negative_count = 0 + for bit in bit_list: + if bit == 1: + positive_count += 1 + elif bit == 0: + negative_count += 1 + else: + raise TypeError("Not a bit: %s" % repr(bit)) + print "%i positive bits and %i negative bits" % (positive_count, negative_count) + @@ -621,16 +744,38 @@ def add_block(self, block_type, block_length, block_bit_list): verbose=False # verbose=True ) - # ~ sys.exit() +# sys.exit() + # created by Xroar Emulator - FILENAME = "HelloWorld1 xroar.wav" - even_odd = False +# FILENAME = "HelloWorld1 xroar.wav" + +# even_odd = False # correct: +# Bit 1 min: 1696Hz avg: 2058.3Hz max: 2205Hz variation: 509Hz +# Bit 0 min: 595Hz avg: 1090.4Hz max: 1160Hz Variation: 565Hz +# 4760 Bits: 2243 positive bits and 2517 negative bits + +# even_odd = True # wrong: +# Bit 1 min: 1470Hz avg: 1487.5Hz max: 2205Hz variation: 735Hz +# Bit 0 min: 689Hz avg: 1332.3Hz max: 1378Hz Variation: 689Hz +# 4760 Bits: 2404 positive bits and 2356 negative bits + + # created by origin Dragon 32 machine -# FILENAME = "HelloWorld1 origin.wav" # 109922 frames, 2735 bits (raw) -# even_odd = True + FILENAME = "HelloWorld1 origin.wav" # 109922 frames, 2735 bits (raw) + even_odd = True # correct: + # Bit 1 min: 1764Hz avg: 2013.9Hz max: 2100Hz variation: 336Hz + # Bit 0 min: 595Hz avg: 1090.2Hz max: 1336Hz Variation: 741Hz + # 2710 Bits: 1217 positive bits and 1493 negative bits + +# even_odd = False # wrong: + # Bit 1 min: 1422Hz avg: 1461.5Hz max: 2100Hz variation: 678Hz + # Bit 0 min: 459Hz avg: 1265.1Hz max: 1378Hz Variation: 919Hz + # 2712 Bits: 1723 positive bits and 989 negative bits + + """ The origin BASIC code of the two WAV file is: @@ -650,8 +795,18 @@ def add_block(self, block_type, block_length, block_bit_list): # FILENAME = "Quickbeam Software - Duplicas v3.0.wav" # binary! # even_odd = False + # FILENAME = "Dragon Data Ltd - Examples from the Manual - 39~58 [run].wav" -# even_odd = False +# even_odd = False # correct: + # Bit 1 min: 1696Hz avg: 2004.0Hz max: 2004Hz variation: 308Hz + # Bit 0 min: 1025Hz avg: 1025.0Hz max: 1025Hz Variation: 0Hz + # 155839 Bits: 73776 positive bits and 82063 negative bits + +# even_odd = True # wrong: + # Bit 1 min: 2004Hz avg: 2004.5Hz max: 2940Hz variation: 936Hz + # Bit 0 min: 1025Hz avg: 1330.1Hz max: 1378Hz Variation: 353Hz + # 155840 Bits: 4018 positive bits and 151822 negative bits + # FILENAME = "1_MANIA.WAV" # 148579 frames, 4879 bits (raw) # FILENAME = "2_DBJ.WAV" # TODO @@ -677,14 +832,17 @@ def add_block(self, block_type, block_length, block_bit_list): print "DONE in %s" % human_duration(time.time() - start_time) print "Convert WAVE samples to binary data..." - bit_list = list(samples2bits(wave_samples, framerate, frame_count, even_odd)) - - # print "-"*79 - # print_bitlist(bit_list) - # print "-"*79 - # print_block_table(bit_list) - # print "-"*79 - # sys.exit() + bit_generator = samples2bits(wave_samples, framerate, frame_count, even_odd) + bit_list = array.array('B', bit_generator) # 1.1sec 17KB/s + + print_bit_list_stats(bit_list) + +# print "-"*79 +# print_bitlist(bit_list) +# print "-"*79 +# print_block_table(bit_list) +# print "-"*79 +# sys.exit() cassette = Cassette() diff --git a/utils.py b/utils.py index b1e8cd4d..d8030039 100644 --- a/utils.py +++ b/utils.py @@ -90,6 +90,23 @@ def update(self, count): human_eta = human_duration(eta) return rest, human_eta, smoothed_rate + +def average(old_avg, current_value, count): + """ + Calculate the average. Count must start with 0 + + >>> average(None, 3.23, 0) + 3.23 + >>> average(0, 1, 0) + 1.0 + >>> average(2.5, 5, 4) + 3.0 + """ + if old_avg is None: + return current_value + return (float(old_avg) * count + current_value) / (count + 1) + + if __name__ == "__main__": import doctest print doctest.testmod() From c7a1b1d306229e6c67d1f2782fd724003775b5f4 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Tue, 20 Aug 2013 09:38:37 +0200 Subject: [PATCH 016/151] use array instead of struct.unpack for getting wave values --- dragon32_CAS_decode.py | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/dragon32_CAS_decode.py b/dragon32_CAS_decode.py index 1dc3923f..c2ef9858 100755 --- a/dragon32_CAS_decode.py +++ b/dragon32_CAS_decode.py @@ -30,9 +30,9 @@ import collections import wave import sys -import struct import time import array +import functools try: import audioop @@ -71,10 +71,10 @@ WAVE_RESAMPLE = None # Don't change sample rate 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 +WAV_ARRAY_TYPECODE = { + 1: "b", # 8-bit wave file + 2: "h", # 16-bit wave file + 4: "l", # 32-bit wave file TODO: Test it } MIN_TOGGLE_COUNT = 4 # How many samples must be in pos/neg to count a cycle? @@ -153,10 +153,10 @@ def iter_wave_values(wavefile): samplewidth = wavefile.getsampwidth() # 1 for 8-bit, 2 for 16-bit, 4 for 32-bit samples try: - struct_unpack_str = WAV_UNPACK_STR[samplewidth] + typecode = WAV_ARRAY_TYPECODE[samplewidth] except KeyError: raise NotImplementedError( - "Only %s wave files are supported, yet!" % ", ".join(["%sBit" % (i * 8) for i in WAV_UNPACK_STR.keys()]) + "Only %s wave files are supported, yet!" % ", ".join(["%sBit" % (i * 8) for i in WAV_ARRAY_TYPECODE.keys()]) ) def _print_status(frame_no, framerate): @@ -183,10 +183,9 @@ def _print_status(frame_no, framerate): frame_no = 0 ratecv_state = None - while True: - frames = wavefile.readframes(WAVE_READ_SIZE) - if not frames: - break + + get_wave_block_func = functools.partial(wavefile.readframes, WAVE_READ_SIZE) + for frames in iter(get_wave_block_func, ""): if new_rate is not None: # downsample the wave file @@ -201,11 +200,9 @@ def _print_status(frame_no, framerate): next_status = time.time() + 1 _print_status(frame_no, framerate) - frame_count = len(frames) / samplewidth - frames = struct.unpack(struct_unpack_str % frame_count, frames) - for frame in frames: + for value in array.array(typecode, frames): frame_no += 1 - yield frame_no, frame + yield frame_no, value _print_status(frame_no, framerate) print @@ -749,9 +746,9 @@ def print_bit_list_stats(bit_list): # created by Xroar Emulator -# FILENAME = "HelloWorld1 xroar.wav" + FILENAME = "HelloWorld1 xroar.wav" # 8Bit 22050Hz -# even_odd = False # correct: + even_odd = False # correct: # Bit 1 min: 1696Hz avg: 2058.3Hz max: 2205Hz variation: 509Hz # Bit 0 min: 595Hz avg: 1090.4Hz max: 1160Hz Variation: 565Hz # 4760 Bits: 2243 positive bits and 2517 negative bits @@ -764,8 +761,9 @@ def print_bit_list_stats(bit_list): # created by origin Dragon 32 machine - FILENAME = "HelloWorld1 origin.wav" # 109922 frames, 2735 bits (raw) - even_odd = True # correct: + # 16Bit 44.1KHz mono +# FILENAME = "HelloWorld1 origin.wav" # 109922 frames, 2735 bits (raw) +# even_odd = True # correct: # Bit 1 min: 1764Hz avg: 2013.9Hz max: 2100Hz variation: 336Hz # Bit 0 min: 595Hz avg: 1090.2Hz max: 1336Hz Variation: 741Hz # 2710 Bits: 1217 positive bits and 1493 negative bits @@ -812,6 +810,9 @@ def print_bit_list_stats(bit_list): # FILENAME = "2_DBJ.WAV" # TODO # even_odd = False +# FILENAME = "LineNumber Test 01.wav" # TODO +# even_odd = True + print "Read '%s'..." % FILENAME wavefile = wave.open(FILENAME, "r") From 602902430c98cc4da818715c0f683ead45eacf43 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Tue, 20 Aug 2013 14:15:08 +0200 Subject: [PATCH 017/151] Handle pointer and line numbers in the right way. --- LineNumber Test 01.wav | Bin 0 -> 264236 bytes dragon32_CAS_decode.py | 197 +++++++++++++++++++++++++++++------------ 2 files changed, 138 insertions(+), 59 deletions(-) create mode 100755 LineNumber Test 01.wav diff --git a/LineNumber Test 01.wav b/LineNumber Test 01.wav new file mode 100755 index 0000000000000000000000000000000000000000..cb55bfc2cbc4947e8efce8c8ffca384eac7101b2 GIT binary patch literal 264236 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 )jhJ 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 literal 0 HcmV?d00001 diff --git a/dragon32_CAS_decode.py b/dragon32_CAS_decode.py index c2ef9858..637870cd 100755 --- a/dragon32_CAS_decode.py +++ b/dragon32_CAS_decode.py @@ -33,6 +33,7 @@ import time import array import functools +import itertools try: import audioop @@ -555,16 +556,79 @@ def get_block_info(bit_list): return bit_list, block_type, block_length +def to16bit(values): + """ + >>> v = to16bit([0x1e, 0x12]) + >>> v + 7698 + >>> hex(v) + '0x1e12' + """ + return (values[0] << 8) | values[1] + + +def get_until(g, until): + """ + >>> g=iter([1,2,3,4,5]) + >>> r=get_until(g, 3) + >>> list(r) + [1, 2] + """ + while True: + item = g.next() + if item == until: + raise StopIteration() + yield item + + +def get_16bit_char(g): + """ + >>> g=iter([0x1e, 0x12]) + >>> v=get_16bit_char(g) + >>> v + 7698 + >>> hex(v) + '0x1e12' + """ + values = itertools.islice(g, 2) + values = list(values) + return to16bit(values) + + +def bytes2codeline(raw_bytes): + """ + >>> data = (0x87,0x20,0x22,0x48,0x45,0x4c,0x4c,0x4f,0x20,0x57,0x4f,0x52,0x4c,0x44,0x21,0x22) + >>> bytes2codeline(data) + 'PRINT "HELLO WORLD!"' + """ + code_line = "" + func_token = False + for byte_no in raw_bytes: + if byte_no == 0xff: # Next byte is a function token + func_token = True + continue + elif func_token == True: + func_token = False + character = FUNCTION_TOKEN[byte_no] + elif byte_no in BASIC_TOKENS: + character = BASIC_TOKENS[byte_no] + else: + character = chr(byte_no) +# print byte_no, repr(character) + code_line += character + return code_line + + class CodeLine(object): - def __init__(self, pre_bytes, line_no, code): + def __init__(self, line_pointer, line_no, code): assert isinstance(line_no, int), "Line number not integer, it's: %s" % repr(line_no) - self.pre_bytes = pre_bytes + self.line_pointer = line_pointer self.line_no = line_no self.code = code def __repr__(self): - return "" % ( - repr(self.pre_bytes), repr(self.line_no), repr(self.code) + return "" % ( + repr(self.line_pointer), repr(self.line_no), repr(self.code) ) @@ -578,70 +642,84 @@ def __init__(self): def add_data_block(self, block_length, block_bit_list): """ >>> fc = FileContent() - >>> data = ( - ... 0x1e,0x12,0x0,0xa,0x80,0x20,0x49,0x20,0xcb,0x20,0x31,0x20,0xbc,0x20,0x31,0x30,0x0,0x1e,0x29,0x0,0x14,0x87,0x20,0x49,0x3b,0x22,0x48,0x45,0x4c,0x4c,0x4f,0x20,0x57,0x4f,0x52,0x4c,0x44,0x21,0x22,0x0,0x1e,0x31,0x0,0x1e,0x8b,0x20,0x49,0x0,0x0,0x0 - ... ) - >>> bit_list=byte_list2bit_list(data) - >>> fc.add_data_block(0, bit_list) + + >>> block = byte_list2bit_list([ + ... 0x1e,0x12,0x0,0xa,0x80,0x20,0x49,0x20,0xcb,0x20,0x31,0x20,0xbc,0x20,0x31,0x30,0x0, + ... 0x0,0x0]) + >>> fc.add_data_block(0,block) + >>> fc.print_code_lines() + 10 FOR I = 1 TO 10 + + >>> block = byte_list2bit_list([ + ... 0x1e,0x29,0x0,0x14,0x87,0x20,0x49,0x3b,0x22,0x48,0x45,0x4c,0x4c,0x4f,0x20,0x57,0x4f,0x52,0x4c,0x44,0x21,0x22,0x0, + ... 0x0,0x0]) + >>> fc.add_data_block(0,block) + >>> fc.print_code_lines() + 10 FOR I = 1 TO 10 + 20 PRINT I;"HELLO WORLD!" + + >>> block = byte_list2bit_list([ + ... 0x1e,0x31,0x0,0x1e,0x8b,0x20,0x49,0x0, + ... 0x0,0x0]) + >>> fc.add_data_block(0,block) >>> fc.print_code_lines() 10 FOR I = 1 TO 10 20 PRINT I;"HELLO WORLD!" 30 NEXT I + + Test function tokens in code + >>> fc = FileContent() >>> data = ( - ... 0x1e,0x4a,0x0, - ... 0x1e,0x58,0xcb,0x58,0xc3,0x4c,0xc5,0xff,0x88,0x28,0x52,0x29,0x3a,0x59,0xcb,0x59,0xc3,0x4c,0xc5,0xff,0x89,0x28,0x52,0x29, - ... 0x0,0x0,0x0 + ... 0x1e,0x4a,0x0,0x1e,0x58,0xcb,0x58,0xc3,0x4c,0xc5,0xff,0x88,0x28,0x52,0x29,0x3a,0x59,0xcb,0x59,0xc3,0x4c,0xc5,0xff,0x89,0x28,0x52,0x29,0x0, + ... 0x0,0x0 ... ) >>> bit_list=byte_list2bit_list(data) >>> fc.add_data_block(0, bit_list) >>> fc.print_code_lines() 30 X=X+L*SIN(R):Y=Y+L*COS(R) + + + Test high line numbers + + >>> fc = FileContent() + >>> 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 + ... ) + >>> bit_list=byte_list2bit_list(data) + >>> fc.add_data_block(0, bit_list) + >>> fc.print_code_lines() + 1 PRINT "LINE NUMBER TEST" + 10 PRINT 10 + 100 PRINT 100 + 1000 PRINT 1000 + 10000 PRINT 10000 + 32768 PRINT 32768 + 63999 PRINT "END";63999 """ - in_code_line = False - func_token = False - pre_bytes = [] - line_no = None - code_line = "" - - raw_bytes = [bits2byte_no(bit_block) for bit_block in block_bit_list] - for index, byte_no in enumerate(raw_bytes): -# print index, hex(byte_no) - if byte_no == 0x00: - if in_code_line: - # print "Add code line", repr(pre_bytes), repr(line_no), repr(code_line) - code_line_obj = CodeLine(pre_bytes, line_no, code_line) - self.code_lines.append(code_line_obj) - pre_bytes = [] - line_no = None - code_line = "" - in_code_line = False - else: - if raw_bytes[index:] == [0, 0]: - # Next two bytes are 0x00 0x00 -> end of data delimiter - break - in_code_line = True - else: - if in_code_line: - if line_no is None: - line_no = byte_no - continue - - if byte_no == 0xff: # Next byte is a function token - func_token = True - continue - elif func_token == True: - func_token = False - character = FUNCTION_TOKEN[byte_no] - elif byte_no in BASIC_TOKENS: - character = BASIC_TOKENS[byte_no] - else: - character = chr(byte_no) - # print byte_no, repr(character) - code_line += character - else: - pre_bytes.append(byte_no) + data = iter([bits2byte_no(bit_block) for bit_block in block_bit_list]) + 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 = get_until(data, 0x00) + code = bytes2codeline(code) + + self.code_lines.append( + CodeLine(line_pointer, line_number, code) + ) def print_code_lines(self): for code_line in self.code_lines: @@ -746,9 +824,9 @@ def print_bit_list_stats(bit_list): # created by Xroar Emulator - FILENAME = "HelloWorld1 xroar.wav" # 8Bit 22050Hz +# FILENAME = "HelloWorld1 xroar.wav" # 8Bit 22050Hz - even_odd = False # correct: +# even_odd = False # correct: # Bit 1 min: 1696Hz avg: 2058.3Hz max: 2205Hz variation: 509Hz # Bit 0 min: 595Hz avg: 1090.4Hz max: 1160Hz Variation: 565Hz # 4760 Bits: 2243 positive bits and 2517 negative bits @@ -810,8 +888,9 @@ def print_bit_list_stats(bit_list): # FILENAME = "2_DBJ.WAV" # TODO # even_odd = False -# FILENAME = "LineNumber Test 01.wav" # TODO -# even_odd = True + # BASIC file with high line numbers: + FILENAME = "LineNumber Test 01.wav" + even_odd = True print "Read '%s'..." % FILENAME From 4859409025f188261253e1507fea91af9df13e4e Mon Sep 17 00:00:00 2001 From: Jens Diemer Date: Tue, 20 Aug 2013 06:59:14 -0700 Subject: [PATCH 018/151] Initial commit --- .gitignore | 35 +++ LICENSE | 674 +++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 4 + 3 files changed, 713 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..d2d6f360 --- /dev/null +++ b/.gitignore @@ -0,0 +1,35 @@ +*.py[cod] + +# C extensions +*.so + +# Packages +*.egg +*.egg-info +dist +build +eggs +parts +bin +var +sdist +develop-eggs +.installed.cfg +lib +lib64 + +# Installer logs +pip-log.txt + +# Unit test / coverage reports +.coverage +.tox +nosetests.xml + +# Translations +*.mo + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..16d60a46 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ +GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. {http://fsf.org/} + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {one line to give the program's name and a brief idea of what it does.} + Copyright (C) {year} {name of author} + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + PyDragon32 Copyright (C) 2013 Jens Diemer + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +{http://www.gnu.org/licenses/}. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +{http://www.gnu.org/philosophy/why-not-lgpl.html}. diff --git a/README.md b/README.md new file mode 100644 index 00000000..27d51124 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +PyDragon32 +========== + +Python tools/script around Dragon32/64 homecomputer From 863e46e2602bd722e0ee8eaf8732f0a90343d3d7 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Tue, 20 Aug 2013 16:10:27 +0200 Subject: [PATCH 019/151] move files --- .../HelloWorld1 origin.wav | Bin .../HelloWorld1 xroar.wav | Bin .../LineNumber Test 01.wav | Bin dragon32_CAS_decode.py => PyDC/PyDC.py | 9 ------- PyDC/README.creole | 23 ++++++++++++++++++ basic_tokens.py => PyDC/basic_tokens.py | 0 utils.py => PyDC/utils.py | 0 7 files changed, 23 insertions(+), 9 deletions(-) rename HelloWorld1 origin.wav => PyDC/HelloWorld1 origin.wav (100%) rename HelloWorld1 xroar.wav => PyDC/HelloWorld1 xroar.wav (100%) rename LineNumber Test 01.wav => PyDC/LineNumber Test 01.wav (100%) rename dragon32_CAS_decode.py => PyDC/PyDC.py (95%) create mode 100644 PyDC/README.creole rename basic_tokens.py => PyDC/basic_tokens.py (100%) rename utils.py => PyDC/utils.py (100%) diff --git a/HelloWorld1 origin.wav b/PyDC/HelloWorld1 origin.wav similarity index 100% rename from HelloWorld1 origin.wav rename to PyDC/HelloWorld1 origin.wav diff --git a/HelloWorld1 xroar.wav b/PyDC/HelloWorld1 xroar.wav similarity index 100% rename from HelloWorld1 xroar.wav rename to PyDC/HelloWorld1 xroar.wav diff --git a/LineNumber Test 01.wav b/PyDC/LineNumber Test 01.wav similarity index 100% rename from LineNumber Test 01.wav rename to PyDC/LineNumber Test 01.wav diff --git a/dragon32_CAS_decode.py b/PyDC/PyDC.py similarity index 95% rename from dragon32_CAS_decode.py rename to PyDC/PyDC.py index 637870cd..6d295063 100755 --- a/dragon32_CAS_decode.py +++ b/PyDC/PyDC.py @@ -13,15 +13,6 @@ - add cli - write .BAS file - Spec links: - http://www.onastick.clara.net/cosio.htm - http://www.cs.unc.edu/~yakowenk/coco/text/tapeformat.html - http://dragon32.info/info/basicfmt.html - - Many thanks to the people from: - http://www.python-forum.de/viewtopic.php?f=1&t=32102 (de) - http://archive.worldofdragon.org/phpBB3/viewtopic.php?f=8&t=4231 (en) - :copyleft: 2013 by Jens Diemer :license: GNU GPL v3 or above, see LICENSE for more details. """ diff --git a/PyDC/README.creole b/PyDC/README.creole new file mode 100644 index 00000000..2104b18f --- /dev/null +++ b/PyDC/README.creole @@ -0,0 +1,23 @@ + +== PyDC - Python Dragon 32 converter + +Convert dragon 32 Cassetts WAV files into plain text. + + +copyleft: 2013 by Jens Diemer +license: GNU GPL v3 or above, see LICENSE for more details. + + +=== Links + + +Spec links: + * http://www.onastick.clara.net/cosio.htm + * http://www.cs.unc.edu/~yakowenk/coco/text/tapeformat.html + * http://dragon32.info/info/basicfmt.html + * http://archive.worldofdragon.org/index.php?title=Tape\Disk_Preservation#File_Formats + + +Many thanks to the people from: + * http://archive.worldofdragon.org/phpBB3/viewtopic.php?f=8&t=4231 (en) + * http://www.python-forum.de/viewtopic.php?f=1&t=32102 (de) \ No newline at end of file diff --git a/basic_tokens.py b/PyDC/basic_tokens.py similarity index 100% rename from basic_tokens.py rename to PyDC/basic_tokens.py diff --git a/utils.py b/PyDC/utils.py similarity index 100% rename from utils.py rename to PyDC/utils.py From c964ad085e554b094a16da874c48031375e093d8 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Tue, 20 Aug 2013 16:11:52 +0200 Subject: [PATCH 020/151] update README --- README.creole | 6 ++++++ README.md | 4 ---- 2 files changed, 6 insertions(+), 4 deletions(-) create mode 100644 README.creole delete mode 100644 README.md diff --git a/README.creole b/README.creole new file mode 100644 index 00000000..c2e4d8ab --- /dev/null +++ b/README.creole @@ -0,0 +1,6 @@ +== PyDragon32 + +Some Python tools/script around Dragon32/64 homecomputer: + +* PyDC - Convert dragon 32 Cassetts WAV files into plain text: +** https://github.com/jedie/PyDragon32/tree/master/PyDC diff --git a/README.md b/README.md deleted file mode 100644 index 27d51124..00000000 --- a/README.md +++ /dev/null @@ -1,4 +0,0 @@ -PyDragon32 -========== - -Python tools/script around Dragon32/64 homecomputer From b794de7ca8671c1954ad6ec733d9c682ff51ddc1 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Tue, 20 Aug 2013 16:40:13 +0200 Subject: [PATCH 021/151] bytes2codeline(): even easier --- PyDC/PyDC.py | 48 +++++++++++++----------------------------------- 1 file changed, 13 insertions(+), 35 deletions(-) diff --git a/PyDC/PyDC.py b/PyDC/PyDC.py index 6d295063..f4b8add0 100755 --- a/PyDC/PyDC.py +++ b/PyDC/PyDC.py @@ -547,43 +547,18 @@ def get_block_info(bit_list): return bit_list, block_type, block_length -def to16bit(values): +def get_word(byte_iterator): """ - >>> v = to16bit([0x1e, 0x12]) - >>> v - 7698 - >>> hex(v) - '0x1e12' - """ - return (values[0] << 8) | values[1] - - -def get_until(g, until): - """ - >>> g=iter([1,2,3,4,5]) - >>> r=get_until(g, 3) - >>> list(r) - [1, 2] - """ - while True: - item = g.next() - if item == until: - raise StopIteration() - yield item + return a uint16 value - -def get_16bit_char(g): - """ >>> g=iter([0x1e, 0x12]) - >>> v=get_16bit_char(g) + >>> v=get_word(g) >>> v 7698 >>> hex(v) '0x1e12' """ - values = itertools.islice(g, 2) - values = list(values) - return to16bit(values) + return (next(byte_iterator) << 8) | next(byte_iterator) def bytes2codeline(raw_bytes): @@ -697,15 +672,18 @@ def add_data_block(self, block_length, block_bit_list): """ data = iter([bits2byte_no(bit_block) for bit_block in block_bit_list]) while True: - line_pointer = list(itertools.islice(data, 2)) - if line_pointer == [0x00, 0x00]: - # end of block + line_pointer = get_word(data) + if not line_pointer: + # arrived [0x00, 0x00] -> end of block break - line_pointer = to16bit(line_pointer) - line_number = get_16bit_char(data) + line_number = get_word(data) + + # get the code line: + # new iterator to get all characters until 0x00 arraived + code = iter(data.next, 0x00) - code = get_until(data, 0x00) + # convert to a plain ASCII string code = bytes2codeline(code) self.code_lines.append( From 4e0954db9c05ecff337bade8c3b2183a1d0c3d2f Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Tue, 20 Aug 2013 18:32:58 +0200 Subject: [PATCH 022/151] start to support ASCII BASIC files, but not done, yet. --- PyDC/PyDC.py | 132 +++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 107 insertions(+), 25 deletions(-) diff --git a/PyDC/PyDC.py b/PyDC/PyDC.py index f4b8add0..241f3936 100755 --- a/PyDC/PyDC.py +++ b/PyDC/PyDC.py @@ -422,6 +422,9 @@ def pop_bytes_from_bit_list(bit_list, count): >>> bytes [[1, 1, 0, 0, 1, 1, 0, 0]] """ + + + data_bit_count = count * 8 data_bit_list = bit_list[:data_bit_count] @@ -473,6 +476,29 @@ def bits2byte_no(bits): bits = list2str(bits) return int(bits, 2) +def bit_blocks2byte_no(block_bit_list): + """ + >>> bit_list = ( + ... [0,0,1,1,0,0,1,0], # L + ... [1,0,0,1,0,0,1,0], # I + ... ) + >>> bit_blocks2byte_no(bit_list) + [76, 73] + """ + byte_no = [bits2byte_no(block) for block in block_bit_list] + return byte_no + +def bit_blocks2string(block_bit_list): + """ + >>> bit_list = ( + ... [0,0,1,1,0,0,1,0], # L + ... [1,0,0,1,0,0,1,0], # I + ... ) + >>> bit_blocks2string(bit_list) + 'LI' + """ + bytes = "".join([chr(byte_no) for byte_no in bit_blocks2byte_no(block_bit_list)]) + return bytes def byte_list2bit_list(data): """ @@ -487,11 +513,6 @@ def byte_list2bit_list(data): bit_list.append(bits) return bit_list -def block2bytes(block_bit_list): - bytes = "".join([chr(bits2byte_no(block)) for block in block_bit_list]) - return bytes - - def print_block_table(block_bit_list): for block in block_bit_list: byte_no = bits2byte_no(block) @@ -605,21 +626,23 @@ class FileContent(object): def __init__(self): self.code_lines = [] - def add_data_block(self, block_length, block_bit_list): + def add_tokenized_block(self, block_length, block_bit_list): """ + add a block of tokenized BASIC source code lines. + >>> fc = FileContent() >>> block = byte_list2bit_list([ ... 0x1e,0x12,0x0,0xa,0x80,0x20,0x49,0x20,0xcb,0x20,0x31,0x20,0xbc,0x20,0x31,0x30,0x0, ... 0x0,0x0]) - >>> fc.add_data_block(0,block) + >>> fc.add_tokenized_block(0,block) >>> fc.print_code_lines() 10 FOR I = 1 TO 10 >>> block = byte_list2bit_list([ ... 0x1e,0x29,0x0,0x14,0x87,0x20,0x49,0x3b,0x22,0x48,0x45,0x4c,0x4c,0x4f,0x20,0x57,0x4f,0x52,0x4c,0x44,0x21,0x22,0x0, ... 0x0,0x0]) - >>> fc.add_data_block(0,block) + >>> fc.add_tokenized_block(0,block) >>> fc.print_code_lines() 10 FOR I = 1 TO 10 20 PRINT I;"HELLO WORLD!" @@ -627,7 +650,7 @@ def add_data_block(self, block_length, block_bit_list): >>> block = byte_list2bit_list([ ... 0x1e,0x31,0x0,0x1e,0x8b,0x20,0x49,0x0, ... 0x0,0x0]) - >>> fc.add_data_block(0,block) + >>> fc.add_tokenized_block(0,block) >>> fc.print_code_lines() 10 FOR I = 1 TO 10 20 PRINT I;"HELLO WORLD!" @@ -642,7 +665,7 @@ def add_data_block(self, block_length, block_bit_list): ... 0x0,0x0 ... ) >>> bit_list=byte_list2bit_list(data) - >>> fc.add_data_block(0, bit_list) + >>> fc.add_tokenized_block(0, bit_list) >>> fc.print_code_lines() 30 X=X+L*SIN(R):Y=Y+L*COS(R) @@ -660,7 +683,7 @@ def add_data_block(self, block_length, block_bit_list): ... 0x1e,0x62,0xf9,0xff,0x87,0x20,0x22,0x45,0x4e,0x44,0x22,0x3b,0x36,0x33,0x39,0x39,0x39,0x0,0x0,0x0 ... ) >>> bit_list=byte_list2bit_list(data) - >>> fc.add_data_block(0, bit_list) + >>> fc.add_tokenized_block(0, bit_list) >>> fc.print_code_lines() 1 PRINT "LINE NUMBER TEST" 10 PRINT 10 @@ -690,6 +713,38 @@ def add_data_block(self, block_length, block_bit_list): CodeLine(line_pointer, line_number, code) ) + + def add_ascii_block(self, block_length, block_bit_list): + """ + add a block of ASCII BASIC source code lines. + """ +# data = iter(bit_blocks2byte_no(block_bit_list)) +# +# while True: +# # get the code line: +# # new iterator to get all characters until 0x00 arraived +# code = iter(data.next, 0xd) +# if not code: +# break +# +# print repr(list(code)) +# +# line = "" +# for byte_no in code: +# line += chr(byte_no) +# print line +# if not line: +# break + + + for bit_block in block_bit_list: + byte_no = bits2byte_no(bit_block) + character = chr(byte_no) + print byte_no, repr(character) + + raise TODO + + def print_code_lines(self): for code_line in self.code_lines: print "%i %s" % (code_line.line_no, code_line.code) @@ -717,31 +772,55 @@ class CassetteFile(object): 5.6 Two bytes for the default load address of a binary file. """ - def __init__(self, file_block_bit_list): + def __init__(self, file_block_data): # print_block_bit_list(block_bit_list) print_block_table(block_bit_list) - print_as_hex_list(file_block_bit_list) + print_as_hex_list(file_block_data) + + self.filename = bit_blocks2string(block_bit_list[:8]) + + byte_no_block = bit_blocks2byte_no(file_block_data[8:]) + print "file meta:", repr(byte_no_block) + + self.file_type = byte_no_block[0] + print "file type:", repr(self.file_type) + if self.file_type == 0x00: + print "BASIC programm (0x00)" + elif self.file_type == 0x01: + print "Data file (0x01)" + raise NotImplemented("Data files are not supported, yet.") + elif self.file_type == 0xFF: + print "Binary file (0xFF)" + raise NotImplemented("Binary files are not supported, yet.") + else: + raise NotImplemented( + "Unknown file type %s is not supported, yet." % hex(self.file_type) + ) - self.data = block2bytes(block_bit_list) - self.filename = self.data[:8] + ascii_flag = byte_no_block[1] + print "ASCII Flag is:", repr(ascii_flag) + if ascii_flag == 0x00: + print "tokenized BASIC" + self.is_tokenized = True + elif ascii_flag == 0xff: + print "ASCII BASIC" + self.is_tokenized = False - self.file_type = self.data[8] - print "file type:", repr(self.file_type), -# if self.file_type == 0x00: -# print "BASIC programm" -# if self.file_type == 0x00: self.file_content = FileContent() - def add_data_block(self, block_length, block_bit_list): + def add_tokenized_block(self, block_length, block_bit_list): print_as_hex_list(block_bit_list) - self.file_content.add_data_block(block_length, block_bit_list) + if self.is_tokenized: + self.file_content.add_tokenized_block(block_length, block_bit_list) + else: + self.file_content.add_ascii_block(block_length, block_bit_list) print "*"*79 self.file_content.print_code_lines() print "*"*79 def __repr__(self): - return "" % (self.filename, repr(self.data)) + return "" % (self.filename,) class Cassette(object): @@ -757,7 +836,7 @@ def add_block(self, block_type, block_length, block_bit_list): print "Add file %s" % repr(self.current_file) self.files.append(self.current_file) elif block_type == DATA_BLOCK: - self.current_file.add_data_block(block_length, block_bit_list) + self.current_file.add_tokenized_block(block_length, block_bit_list) else: raise TypeError("Block type %s unkown!" & hex(block_type)) @@ -858,7 +937,10 @@ def print_bit_list_stats(bit_list): # even_odd = False # BASIC file with high line numbers: - FILENAME = "LineNumber Test 01.wav" +# FILENAME = "LineNumber Test 01.wav" # tokenized BASIC +# even_odd = False + + FILENAME = "LineNumber Test 02.wav" # ASCII BASIC even_odd = True From 16180d2a583f8ba4a69dadbb0685b88130d21cfb Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Wed, 21 Aug 2013 18:09:39 +0200 Subject: [PATCH 023/151] ASCII BASIC parser works now. --- PyDC/PyDC.py | 50 ++++++++++++++++++++++---------------------------- 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/PyDC/PyDC.py b/PyDC/PyDC.py index 241f3936..133611fa 100755 --- a/PyDC/PyDC.py +++ b/PyDC/PyDC.py @@ -422,9 +422,6 @@ def pop_bytes_from_bit_list(bit_list, count): >>> bytes [[1, 1, 0, 0, 1, 1, 0, 0]] """ - - - data_bit_count = count * 8 data_bit_list = bit_list[:data_bit_count] @@ -541,6 +538,7 @@ def print_as_hex_list(block_bit_list): def get_block_info(bit_list): + # Searching for lead-in byte leader_pos = find_iter_window(bit_list, LEADER_BYTE) # Search for LEADER_BYTE in bit-by-bit steps print "Start leader '%s' found at position: %i" % (LEADER_BYTE, leader_pos) @@ -548,6 +546,7 @@ def get_block_info(bit_list): print "bits before header:", repr(list2str(bit_list[:leader_pos])) bit_list = bit_list[leader_pos:] + # count lead-in byte matches without ceasing to get faster to the sync-byte leader_count = count_continuous_pattern(bit_list, LEADER_BYTE) print "Found %i leader bytes" % leader_count if leader_count == 0: @@ -555,7 +554,9 @@ def get_block_info(bit_list): to_cut = leader_count * 8 bit_list = bit_list[to_cut:] - sync_pos = find_iter_window(bit_list, SYNC_BYTE) # Search for SYNC_BYTE in bit-by-bit steps + # Search for SYNC_BYTE in bit-by-bit steps + # to get a byte-synchronized bit-sequence + sync_pos = find_iter_window(bit_list, SYNC_BYTE) print "Find sync byte after %i Bits" % sync_pos to_cut = sync_pos + 8 # Bits before sync byte + sync byte bit_list = bit_list[to_cut:] @@ -718,32 +719,25 @@ def add_ascii_block(self, block_length, block_bit_list): """ add a block of ASCII BASIC source code lines. """ -# data = iter(bit_blocks2byte_no(block_bit_list)) -# -# while True: -# # get the code line: -# # new iterator to get all characters until 0x00 arraived -# code = iter(data.next, 0xd) -# if not code: -# break -# -# print repr(list(code)) -# -# line = "" -# for byte_no in code: -# line += chr(byte_no) -# print line -# if not line: -# break - - - for bit_block in block_bit_list: - byte_no = bits2byte_no(bit_block) - character = chr(byte_no) - print byte_no, repr(character) + data = iter([bits2byte_no(bit_block) for bit_block in block_bit_list]) + data.next() # Skip first \r + while True: + code = iter(data.next, 0xd) # until \r + code = bytes2codeline(code) + if not code: + break + + try: + line_number, code = code.split(" ", 1) + except ValueError, err: + print "Error splitting linenumber in %s: %s" % (repr(code), err) + break - raise TODO + line_number = int(line_number) + self.code_lines.append( + CodeLine(None, line_number, code) + ) def print_code_lines(self): for code_line in self.code_lines: From 65cfa410dfa734bfdaad1912d93cfda821917447 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Wed, 21 Aug 2013 18:45:57 +0200 Subject: [PATCH 024/151] check block_length in BASIC code parsing --- PyDC/PyDC.py | 98 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 69 insertions(+), 29 deletions(-) diff --git a/PyDC/PyDC.py b/PyDC/PyDC.py index 133611fa..9007f137 100755 --- a/PyDC/PyDC.py +++ b/PyDC/PyDC.py @@ -627,31 +627,37 @@ class FileContent(object): def __init__(self): self.code_lines = [] - def add_tokenized_block(self, block_length, block_bit_list): + def add_block_data(self, block_length, data): """ add a block of tokenized BASIC source code lines. >>> fc = FileContent() - >>> block = byte_list2bit_list([ + >>> block = [ ... 0x1e,0x12,0x0,0xa,0x80,0x20,0x49,0x20,0xcb,0x20,0x31,0x20,0xbc,0x20,0x31,0x30,0x0, - ... 0x0,0x0]) - >>> fc.add_tokenized_block(0,block) + ... 0x0,0x0] + >>> len(block) + 19 + >>> fc.add_block_data(19,iter(block)) + 19 Bytes parsed >>> fc.print_code_lines() 10 FOR I = 1 TO 10 - >>> block = byte_list2bit_list([ + >>> block = iter([ ... 0x1e,0x29,0x0,0x14,0x87,0x20,0x49,0x3b,0x22,0x48,0x45,0x4c,0x4c,0x4f,0x20,0x57,0x4f,0x52,0x4c,0x44,0x21,0x22,0x0, ... 0x0,0x0]) - >>> fc.add_tokenized_block(0,block) + >>> fc.add_block_data(999,block) + 25 Bytes parsed + ERROR: Block length value 999 is not equal to parsed bytes! >>> fc.print_code_lines() 10 FOR I = 1 TO 10 20 PRINT I;"HELLO WORLD!" - >>> block = byte_list2bit_list([ + >>> block = iter([ ... 0x1e,0x31,0x0,0x1e,0x8b,0x20,0x49,0x0, ... 0x0,0x0]) - >>> fc.add_tokenized_block(0,block) + >>> fc.add_block_data(10,block) + 10 Bytes parsed >>> fc.print_code_lines() 10 FOR I = 1 TO 10 20 PRINT I;"HELLO WORLD!" @@ -661,12 +667,12 @@ def add_tokenized_block(self, block_length, block_bit_list): Test function tokens in code >>> fc = FileContent() - >>> data = ( + >>> data = iter([ ... 0x1e,0x4a,0x0,0x1e,0x58,0xcb,0x58,0xc3,0x4c,0xc5,0xff,0x88,0x28,0x52,0x29,0x3a,0x59,0xcb,0x59,0xc3,0x4c,0xc5,0xff,0x89,0x28,0x52,0x29,0x0, ... 0x0,0x0 - ... ) - >>> bit_list=byte_list2bit_list(data) - >>> fc.add_tokenized_block(0, bit_list) + ... ]) + >>> fc.add_block_data(30, data) + 30 Bytes parsed >>> fc.print_code_lines() 30 X=X+L*SIN(R):Y=Y+L*COS(R) @@ -674,7 +680,7 @@ def add_tokenized_block(self, block_length, block_bit_list): Test high line numbers >>> fc = FileContent() - >>> data = ( + >>> 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, @@ -682,9 +688,11 @@ def add_tokenized_block(self, block_length, block_bit_list): ... 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 - ... ) - >>> bit_list=byte_list2bit_list(data) - >>> fc.add_tokenized_block(0, bit_list) + ... ] + >>> len(data) + 99 + >>> fc.add_block_data(99, iter(data)) + 99 Bytes parsed >>> fc.print_code_lines() 1 PRINT "LINE NUMBER TEST" 10 PRINT 10 @@ -694,19 +702,24 @@ def add_tokenized_block(self, block_length, block_bit_list): 32768 PRINT 32768 63999 PRINT "END";63999 """ - data = iter([bits2byte_no(bit_block) for bit_block in block_bit_list]) + byte_count = 0 while True: line_pointer = get_word(data) + byte_count += 2 if not line_pointer: # arrived [0x00, 0x00] -> end of block break line_number = get_word(data) + byte_count += 2 # get the code line: # new iterator to get all characters until 0x00 arraived code = iter(data.next, 0x00) + code = list(code) # for len() + byte_count += len(code) + 1 # from 0x00 consumed in iter() + # convert to a plain ASCII string code = bytes2codeline(code) @@ -714,19 +727,40 @@ def add_tokenized_block(self, block_length, block_bit_list): CodeLine(line_pointer, line_number, code) ) + print "%i Bytes parsed" % byte_count + if block_length != byte_count: + print "ERROR: Block length value %i is not equal to parsed bytes!" % block_length - def add_ascii_block(self, block_length, block_bit_list): + def add_ascii_block(self, block_length, data): """ add a block of ASCII BASIC source code lines. + + >>> data = [ + ... 0xd, + ... 0x31,0x30,0x20,0x50,0x52,0x49,0x4e,0x54,0x20,0x22,0x54,0x45,0x53,0x54,0x22, + ... 0xd, + ... 0x32,0x30,0x20,0x50,0x52,0x49,0x4e,0x54,0x20,0x22,0x48,0x45,0x4c,0x4c,0x4f,0x20,0x57,0x4f,0x52,0x4c,0x44,0x21,0x22, + ... 0xd + ... ] + >>> len(data) + 41 + >>> fc = FileContent() + >>> fc.add_ascii_block(41, iter(data)) + 41 Bytes parsed + >>> fc.print_code_lines() + 10 PRINT "TEST" + 20 PRINT "HELLO WORLD!" """ - data = iter([bits2byte_no(bit_block) for bit_block in block_bit_list]) data.next() # Skip first \r + byte_count = 1 # incl. first \r while True: code = iter(data.next, 0xd) # until \r - code = bytes2codeline(code) + code = "".join([chr(c) for c in code]) if not code: break + byte_count += len(code) + 1 # and \r consumed in iter() + try: line_number, code = code.split(" ", 1) except ValueError, err: @@ -739,6 +773,10 @@ def add_ascii_block(self, block_length, block_bit_list): CodeLine(None, line_number, code) ) + print "%i Bytes parsed" % byte_count + if block_length != byte_count: + print "ERROR: Block length value %i is not equal to parsed bytes!" % block_length + def print_code_lines(self): for code_line in self.code_lines: print "%i %s" % (code_line.line_no, code_line.code) @@ -803,12 +841,14 @@ def __init__(self, file_block_data): self.file_content = FileContent() - def add_tokenized_block(self, block_length, block_bit_list): - print_as_hex_list(block_bit_list) + def add_block_data(self, block_length, block_bit_list): + print "raw data length: %iBytes" % len(block_bit_list) +# print_as_hex_list(block_bit_list) + data = iter([bits2byte_no(bit_block) for bit_block in block_bit_list]) if self.is_tokenized: - self.file_content.add_tokenized_block(block_length, block_bit_list) + self.file_content.add_block_data(block_length, data) else: - self.file_content.add_ascii_block(block_length, block_bit_list) + self.file_content.add_ascii_block(block_length, data) print "*"*79 self.file_content.print_code_lines() print "*"*79 @@ -830,7 +870,7 @@ def add_block(self, block_type, block_length, block_bit_list): print "Add file %s" % repr(self.current_file) self.files.append(self.current_file) elif block_type == DATA_BLOCK: - self.current_file.add_tokenized_block(block_length, block_bit_list) + self.current_file.add_block_data(block_length, block_bit_list) else: raise TypeError("Block type %s unkown!" & hex(block_type)) @@ -931,12 +971,12 @@ def print_bit_list_stats(bit_list): # even_odd = False # BASIC file with high line numbers: -# FILENAME = "LineNumber Test 01.wav" # tokenized BASIC -# even_odd = False - - FILENAME = "LineNumber Test 02.wav" # ASCII BASIC + FILENAME = "LineNumber Test 01.wav" # tokenized BASIC even_odd = True +# FILENAME = "LineNumber Test 02.wav" # ASCII BASIC +# even_odd = True + print "Read '%s'..." % FILENAME wavefile = wave.open(FILENAME, "r") From 534a449e31491ced3ba5979947acbd44279ee919 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Thu, 22 Aug 2013 13:52:29 +0200 Subject: [PATCH 025/151] add a new implementation of wave2bitstream like a schmitt trigger --- PyDC/utils.py | 184 +++++++++++++++++++++++++ PyDC/wave2bitstream.py | 296 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 480 insertions(+) create mode 100644 PyDC/wave2bitstream.py diff --git a/PyDC/utils.py b/PyDC/utils.py index d8030039..505594a7 100644 --- a/PyDC/utils.py +++ b/PyDC/utils.py @@ -7,6 +7,8 @@ import time +import collections +import itertools def human_duration(t): @@ -107,6 +109,188 @@ def average(old_avg, current_value, count): return (float(old_avg) * count + current_value) / (count + 1) +def iter_steps(g, steps): + """ + iterate over 'g' in blocks with a length of the given 'step' count. + + >>> for v in iter_steps([1,2,3,4,5], steps=2): v + [1, 2] + [3, 4] + [5] + >>> for v in iter_steps([1,2,3,4,5,6,7,8,9], steps=3): v + [1, 2, 3] + [4, 5, 6] + [7, 8, 9] + + 12345678 12345678 + 12345678 + >>> bits = [int(i) for i in "0101010101010101111000"] + >>> for v in iter_steps(bits, steps=8): v + [0, 1, 0, 1, 0, 1, 0, 1] + [0, 1, 0, 1, 0, 1, 0, 1] + [1, 1, 1, 0, 0, 0] + """ + values = [] + for value in g: + values.append(value) + if len(values) == steps: + yield list(values) + values = [] + if values: + yield list(values) + + +def iter_window(g, window_size): + """ + interate over 'g' bit-by-bit and yield a window with the given 'window_size' width. + + >>> for v in iter_window([1,2,3,4], window_size=2): v + [1, 2] + [2, 3] + [3, 4] + >>> for v in iter_window([1,2,3,4,5], window_size=3): v + [1, 2, 3] + [2, 3, 4] + [3, 4, 5] + + >>> for v in iter_window([1,2,3,4], window_size=2): + ... v + ... v.append(True) + [1, 2] + [2, 3] + [3, 4] + """ + values = collections.deque(maxlen=window_size) + for value in g: + values.append(value) + if len(values) == window_size: + yield list(values) + +def count_continuous_pattern(bits, pattern): + """ + count 'pattern' matches without ceasing. + + >>> bit_str = ( + ... "00111100" + ... "00111100" + ... "0101") + >>> pos = count_continuous_pattern([int(i) for i in bit_str], "00111100") + >>> bit_str[pos*8:] + '0101' + >>> pos + 2 + + >>> count_continuous_pattern([1,1,1,2,3], "1") + 3 + + >>> count_continuous_pattern([1,2,3], "99") + 0 + + >>> count_continuous_pattern([0,1,0,1], "01") + 2 + """ + pattern_len = len(pattern) + pattern = [int(i) for i in pattern] + for count, data in enumerate(iter_steps(bits, pattern_len), 1): + if data != pattern: + count -= 1 + break + return count + + +def find_iter_window(bit_list, pattern): + """ + Search for 'pattern' in bit-by-bit steps (iter window) + and return the number of bits before the 'pattern' match. + + Useable for slicing all bits before the first 'pattern' match: + + >>> bit_str = "111010111" + >>> pos = find_iter_window([int(i) for i in bit_str], "010") + >>> bit_str[pos:] + '010111' + >>> pos + 3 + + >>> find_iter_window([1,1,1], "0") + 0 + >>> find_iter_window([1,0,0], "1") + 0 + >>> find_iter_window([0,1,0], "1") + 1 + >>> find_iter_window([0,0,1], "1") + 2 + """ + pattern_len = len(pattern) + pattern = [int(i) for i in pattern] + for pos, data in enumerate(iter_window(bit_list, pattern_len)): + if data == pattern: + return pos + return 0 + +# def match_count(g, pattern): +# """ +# >>> match_count([, pattern) +# """ +# # Searching for lead-in byte +# leader_pos = find_iter_window(bit_list, LEAD_IN_PATTERN) # Search for LEAD_IN_PATTERN in bit-by-bit steps +# print "Start leader '%s' found at position: %i" % (LEAD_IN_PATTERN, leader_pos) +# +# # Cut bits before the first 01010101 start leader +# print "bits before header:", repr(list2str(bit_list[:leader_pos])) +# bit_list = bit_list[leader_pos:] +# +# # count lead-in byte matches without ceasing to get faster to the sync-byte +# leader_count = count_continuous_pattern(bit_list, LEAD_IN_PATTERN) + +def diff_info(data): + """ + >>> diff_info([5,5,10,10,5,5,10,10]) + (0, 15) + >>> diff_info([5,10,10,5,5,10,10,5]) + (15, 0) + """ + def get_diff(l): + diff = 0 + for no1, no2 in iter_steps(l, steps=2): + diff += abs(no1 - no2) + return diff + + data1 = data[2:] + diff1 = get_diff(data1) + + data2 = data[1:-1] + diff2 = get_diff(data2) + + return diff1, diff2 + + +def list2str(l): + """ + >>> list2str([0, 0, 0, 1, 0, 0, 1, 0]) + '00010010' + """ + return "".join([str(c) for c in l]) + +def print_block_bit_list(block_bit_list, display_block_count=8): + in_line_count = 0 + + line = "" + for no, block in enumerate(block_bit_list, -display_block_count + 1): + line += "%s " % list2str(block) + in_line_count += 1 + if in_line_count >= display_block_count: + in_line_count = 0 + print "%4s - %s" % (no, line) + line = "" + if in_line_count > 0: + print + +def print_bitlist(bit_list): + block_bit_list = iter_steps(bit_list, steps=8) + print_block_bit_list(block_bit_list) + + if __name__ == "__main__": import doctest print doctest.testmod() diff --git a/PyDC/wave2bitstream.py b/PyDC/wave2bitstream.py new file mode 100644 index 00000000..21898ed3 --- /dev/null +++ b/PyDC/wave2bitstream.py @@ -0,0 +1,296 @@ +#!/usr/bin/env python2 +# coding: utf-8 + +""" + :copyleft: 2013 by Jens Diemer + :license: GNU GPL v3 or above, see LICENSE for more details. +""" + +import sys +import wave +import functools +import array +import itertools + +# own modules +from utils import average, diff_info, print_bitlist + + +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 ? + +LEAD_IN_PATTERN = "10101010" # 0x55 + + + +class Wave2Bitstream(object): + + WAVE_READ_SIZE = 16 * 1024 # How many frames should be read from WAVE file at once? + WAV_ARRAY_TYPECODE = { + 1: "b", # 8-bit wave file + 2: "h", # 16-bit wave file + 4: "l", # 32-bit wave file TODO: Test it + } + + # Maximum volume value in wave files: + MAX_VALUES = { + 1: 255, # 8-bit wave file + 2: 32768, # 16-bit wave file + 4: 2147483647, # 32-bit wave file + } + + def __init__(self, wave_filename, lead_in_pattern, mid_volume_ratio=0.2, hysteresis_ratio=0.9): + self.wave_filename = wave_filename + self.lead_in_pattern = lead_in_pattern + self.mid_volume_ratio = mid_volume_ratio + self.hysteresis_ratio = hysteresis_ratio + + + print "open wave file '%s'..." % wave_filename + self.wavefile = wave.open(wave_filename, "rb") + + self.framerate = self.wavefile.getframerate() # frames / second + print "Framerate:", self.framerate + + self.frame_count = self.wavefile.getnframes() + print "Number of audio frames:", self.frame_count + + self.nchannels = self.wavefile.getnchannels() # typically 1 for mono, 2 for stereo + print "channels:", self.nchannels + assert self.nchannels == 1, "Only MONO files are supported, yet!" + + self.samplewidth = self.wavefile.getsampwidth() # 1 for 8-bit, 2 for 16-bit, 4 for 32-bit samples + print "samplewidth: %i (%sBit wave file)" % (self.samplewidth, self.samplewidth * 8) + + # build hysteresis min/max values: + self.trigger_value = self.MAX_VALUES[self.samplewidth] * self.mid_volume_ratio + print "Use trigger value:", self.trigger_value + + # get frame numer + volume value from the WAVE file + self.wave_values = self.iter_wave_values() + + def sync(self, length): + """ + synchronized weave sync trigger + """ + iter_trigger = self.iter_trigger(self.wave_values, half_sinus=True) + + # duration of half sinus cycles + iter_duration = self.iter_duration(iter_trigger) + + test_data = itertools.islice(iter_duration, length) + test_durations = [i[1] for i in test_data] + + diff1, diff2 = diff_info(test_durations) + + if diff1 < diff2: + print "Sync one step." + self.wave_values.next() # FIXME + else: + print "No sync needed." + + def __iter__(self): + # triggered frame numbers of a half sinus cycle + iter_trigger = self.iter_trigger(self.wave_values, half_sinus=False) + + # duration of a complete sinus cycle + iter_duration = self.iter_duration(iter_trigger) + + # build from sinus cycle duration the bit stream + iter_bitstream = self.iter_bitstream(iter_duration) + + return iter_bitstream + + + def iter_bitstream(self, iter_func): + """ + iterate over self.iter_trigger() and + yield the bits + """ + bit_one_min_hz = BIT_ONE_HZ - MAX_HZ_VARIATION + bit_one_max_hz = BIT_ONE_HZ + MAX_HZ_VARIATION + + bit_nul_min_hz = BIT_NUL_HZ - MAX_HZ_VARIATION + bit_nul_max_hz = BIT_NUL_HZ + MAX_HZ_VARIATION + + one_hz_count = 0 + one_hz_min = sys.maxint + one_hz_avg = None + one_hz_max = 0 + nul_hz_count = 0 + nul_hz_min = sys.maxint + nul_hz_avg = None + nul_hz_max = 0 + + bit_count = 0 + + for frame_no, duration in iter_func: + hz = self.framerate / duration +# print "%sHz" % hz + + if hz > bit_one_min_hz and hz < bit_one_max_hz: +# print "bit 1" + bit_count += 1 + yield 1 + one_hz_count += 1 + if hz < one_hz_min: + one_hz_min = hz + if hz > one_hz_max: + one_hz_max = hz + one_hz_avg = average(one_hz_avg, hz, one_hz_count) + elif hz > bit_nul_min_hz and hz < bit_nul_max_hz: +# print "bit 0" + bit_count += 1 + yield 0 + nul_hz_count += 1 + if hz < nul_hz_min: + nul_hz_min = hz + if hz > nul_hz_max: + nul_hz_max = hz + nul_hz_avg = average(nul_hz_avg, hz, nul_hz_count) + else: + print "Skip signal with %sHz." % hz + continue + + print + print "%i Bits: %i positive bits and %i negative bits" % ( + bit_count, one_hz_count, nul_hz_count + ) + print + print "Bit 1: %s-%sHz avg: %.1fHz variation: %sHz" % ( + one_hz_min, one_hz_max, one_hz_avg, one_hz_max - one_hz_min + ) + print "Bit 0: %s-%sHz avg: %.1fHz variation: %sHz" % ( + nul_hz_min, nul_hz_max, nul_hz_avg, nul_hz_max - nul_hz_min + ) + + + def iter_duration(self, iter_trigger): + """ + yield the duration of two frames in a row. + """ + old_frame_no = next(iter_trigger) + for frame_no in iter_trigger: + duration = frame_no - old_frame_no + yield (frame_no, duration) + old_frame_no = frame_no + + def iter_trigger(self, iter_wave_values, half_sinus): + """ + yield only the triggered frame numbers + simmilar to a Schmitt trigger + """ + last_state = False + for frame_no, value in iter_wave_values: + if last_state == False and value > self.trigger_value: + yield frame_no + last_state = True + elif last_state == True and value < -self.trigger_value: + if half_sinus: + yield frame_no + last_state = False + + def iter_wave_values(self): + """ + yield frame numer + volume value from the WAVE file + """ + try: + typecode = self.WAV_ARRAY_TYPECODE[self.samplewidth] + except KeyError: + raise NotImplementedError( + "Only %s wave files are supported, yet!" % ( + ", ".join(["%sBit" % (i * 8) for i in self.WAV_ARRAY_TYPECODE.keys()]) + ) + ) + + frame_no = 0 + get_wave_block_func = functools.partial(self.wavefile.readframes, self.WAVE_READ_SIZE) + for frames in iter(get_wave_block_func, ""): + for value in array.array(typecode, frames): + frame_no += 1 + yield frame_no, value + + +if __name__ == "__main__": + import doctest + print doctest.testmod( + verbose=False + # verbose=True + ) +# sys.exit() + + # BASIC file with high line numbers: + FILENAME = "LineNumber Test 01.wav" # tokenized BASIC + + st = Wave2Bitstream(FILENAME, LEAD_IN_PATTERN) + + st.sync(16) + + print_bitlist(st) + +# for no, i in enumerate(st): + # print no, i +# print i, +# if no > 10: +# print "-- BREAK --" +# break + + print "-- END --" + +""" +132096 frames (wav pos:3.0 sec) eta: 0.0 ms (rate: 67678Frames/sec) +3099 bits decoded from 132096 audio samples in 1.7 sec (0.2KBytes/s) + +Bit 1: 1696-2100Hz avg: 2017.9Hz variation: 404Hz +Bit 0: 432-1191Hz avg: 1088.6Hz variation: 759Hz +3099 Bits: 1374 positive bits and 1725 negative bits + 0 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 + 8 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 + 16 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 + 24 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 + 32 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 + 40 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 + 48 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 + 56 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 + 64 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 + 72 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 + 80 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 + 88 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 + 96 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 + 104 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 + 112 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 + 120 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 + 128 - 10101000 11110000 00000011 11000000 11001010 01001001 11001010 10001001 + 136 - 11001011 11001000 00110010 00110000 00000000 00000000 00000000 00000000 + 144 - 00000000 00000000 00000010 10110010 10101010 10101010 10101010 10101010 + 152 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 + 160 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 + 168 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 + 176 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 + 184 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 + 192 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 + 200 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 + 208 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 + 216 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 + 224 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 + 232 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 + 240 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 + 248 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 + 256 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 + 264 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 + 272 - 10101010 10101010 10101010 10101010 10101010 10100011 11001000 00001100 + 280 - 01100111 10000101 10000000 00001000 00001110 00010000 01000100 01000011 + 288 - 00101001 00100111 00101010 00100000 01000111 00101010 10101011 00100100 + 296 - 00101010 00100100 10100000 01000010 10101010 00101100 10100010 10100100 + 304 - 01000000 00000111 10001100 01000000 00000101 00001110 00010000 01001000 + 312 - 11000000 11000000 00000111 10001011 01000000 00000010 01101110 00010000 + 320 - 01001000 11000000 11000000 11000000 00000111 10000001 11001100 00000001 + 328 - 01111110 00010000 01001000 11000000 11000000 11000000 11000000 00000111 + 336 - 10000010 00101110 01000000 10001110 00010000 01001000 11000000 11000000 + 344 - 11000000 11000000 11000000 00000111 10000000 10100000 00010000 00001110 + 352 - 00010000 01001100 11000100 11001110 11000110 11000001 11000000 00000111 + 360 - 10000100 01101001 11111111 11111110 00010000 01000100 01001010 00100111 + 368 - 00100010 00100100 01001101 11000110 11001100 11001001 11001001 11001001 + 376 - 11000000 00000000 00000000 00001010 11001010 10101010 10100011 11001111 +""" From 87d4fb78cc2ae178348dd65050054296e7a22d9c Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Thu, 22 Aug 2013 16:45:32 +0200 Subject: [PATCH 026/151] sync works, but useless? --- PyDC/wave2bitstream.py | 228 ++++++++++++++++++++++++++--------------- 1 file changed, 143 insertions(+), 85 deletions(-) diff --git a/PyDC/wave2bitstream.py b/PyDC/wave2bitstream.py index 21898ed3..8b857320 100644 --- a/PyDC/wave2bitstream.py +++ b/PyDC/wave2bitstream.py @@ -67,41 +67,63 @@ def __init__(self, wave_filename, lead_in_pattern, mid_volume_ratio=0.2, hystere self.trigger_value = self.MAX_VALUES[self.samplewidth] * self.mid_volume_ratio print "Use trigger value:", self.trigger_value + self.half_sinus = False + + # create the generator chain: + # get frame numer + volume value from the WAVE file - self.wave_values = self.iter_wave_values() + self.wave_values_generator = self.iter_wave_values() + + # triggered frame numbers of a half sinus cycle + self.iter_trigger_generator = self.iter_trigger(self.wave_values_generator) + + # duration of a complete sinus cycle + self.iter_duration_generator = self.iter_duration(self.iter_trigger_generator) + + # build from sinus cycle duration the bit stream + self.iter_bitstream_generator = self.iter_bitstream(self.iter_duration_generator) def sync(self, length): """ synchronized weave sync trigger """ - iter_trigger = self.iter_trigger(self.wave_values, half_sinus=True) + # go in wave stream to the first bit + first_bit_frame_no, first_bit = self.next() + print "First bit is at:", first_bit_frame_no + + print "enable half sinus scan" + self.half_sinus = True + + # Toggle sync test by consuming one half sinus sample +# self.iter_trigger_generator.next() # Test sync - # duration of half sinus cycles - iter_duration = self.iter_duration(iter_trigger) + # get "half sinus cycle" test data + test_durations = itertools.islice(self.iter_duration_generator, length) + # It's a tuple like: [(frame_no, duration)...] - test_data = itertools.islice(iter_duration, length) - test_durations = [i[1] for i in test_data] + test_durations = list(test_durations) + + # Create only a duration list: + test_durations = [i[1] for i in test_durations] +# test_durations = itertools.imap(lambda x: x[1], test_durations) diff1, diff2 = diff_info(test_durations) + print "sync diff info: %i vs. %i" % (diff1, diff2) if diff1 < diff2: print "Sync one step." - self.wave_values.next() # FIXME + self.iter_trigger_generator.next() else: print "No sync needed." - def __iter__(self): - # triggered frame numbers of a half sinus cycle - iter_trigger = self.iter_trigger(self.wave_values, half_sinus=False) - - # duration of a complete sinus cycle - iter_duration = self.iter_duration(iter_trigger) + self.half_sinus = False + print "disable half sinus scan" - # build from sinus cycle duration the bit stream - iter_bitstream = self.iter_bitstream(iter_duration) - - return iter_bitstream + def __iter__(self): + return self + def next(self): + return self.iter_bitstream_generator.next() def iter_bitstream(self, iter_func): """ @@ -132,7 +154,7 @@ def iter_bitstream(self, iter_func): if hz > bit_one_min_hz and hz < bit_one_max_hz: # print "bit 1" bit_count += 1 - yield 1 + yield (frame_no, 1) one_hz_count += 1 if hz < one_hz_min: one_hz_min = hz @@ -142,7 +164,7 @@ def iter_bitstream(self, iter_func): elif hz > bit_nul_min_hz and hz < bit_nul_max_hz: # print "bit 0" bit_count += 1 - yield 0 + yield (frame_no, 0) nul_hz_count += 1 if hz < nul_hz_min: nul_hz_min = hz @@ -176,7 +198,7 @@ def iter_duration(self, iter_trigger): yield (frame_no, duration) old_frame_no = frame_no - def iter_trigger(self, iter_wave_values, half_sinus): + def iter_trigger(self, iter_wave_values): """ yield only the triggered frame numbers simmilar to a Schmitt trigger @@ -184,10 +206,12 @@ def iter_trigger(self, iter_wave_values, half_sinus): last_state = False for frame_no, value in iter_wave_values: if last_state == False and value > self.trigger_value: +# print "half:", self.half_sinus yield frame_no last_state = True elif last_state == True and value < -self.trigger_value: - if half_sinus: + if self.half_sinus: +# print "half sinus!" yield frame_no last_state = False @@ -220,77 +244,111 @@ def iter_wave_values(self): ) # sys.exit() - # BASIC file with high line numbers: - FILENAME = "LineNumber Test 01.wav" # tokenized BASIC +# FILENAME = "HelloWorld1 xroar.wav" # 8Bit 22050Hz + FILENAME = "HelloWorld1 origin.wav" # 109922 frames, 16Bit wave, 44100Hz +# FILENAME = "LineNumber Test 01.wav" # tokenized BASIC + st = Wave2Bitstream(FILENAME, LEAD_IN_PATTERN) - st.sync(16) + bitstream = iter(st) - print_bitlist(st) + bitstream.sync(16) -# for no, i in enumerate(st): - # print no, i -# print i, -# if no > 10: -# print "-- BREAK --" -# break + bitstream = itertools.imap(lambda x: x[1], bitstream) + print_bitlist(bitstream) print "-- END --" """ -132096 frames (wav pos:3.0 sec) eta: 0.0 ms (rate: 67678Frames/sec) -3099 bits decoded from 132096 audio samples in 1.7 sec (0.2KBytes/s) - -Bit 1: 1696-2100Hz avg: 2017.9Hz variation: 404Hz -Bit 0: 432-1191Hz avg: 1088.6Hz variation: 759Hz -3099 Bits: 1374 positive bits and 1725 negative bits - 0 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 - 8 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 - 16 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 - 24 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 - 32 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 - 40 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 - 48 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 - 56 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 - 64 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 - 72 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 - 80 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 - 88 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 - 96 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 - 104 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 - 112 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 - 120 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 - 128 - 10101000 11110000 00000011 11000000 11001010 01001001 11001010 10001001 - 136 - 11001011 11001000 00110010 00110000 00000000 00000000 00000000 00000000 - 144 - 00000000 00000000 00000010 10110010 10101010 10101010 10101010 10101010 - 152 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 - 160 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 - 168 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 - 176 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 - 184 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 - 192 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 - 200 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 - 208 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 - 216 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 - 224 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 - 232 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 - 240 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 - 248 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 - 256 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 - 264 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 - 272 - 10101010 10101010 10101010 10101010 10101010 10100011 11001000 00001100 - 280 - 01100111 10000101 10000000 00001000 00001110 00010000 01000100 01000011 - 288 - 00101001 00100111 00101010 00100000 01000111 00101010 10101011 00100100 - 296 - 00101010 00100100 10100000 01000010 10101010 00101100 10100010 10100100 - 304 - 01000000 00000111 10001100 01000000 00000101 00001110 00010000 01001000 - 312 - 11000000 11000000 00000111 10001011 01000000 00000010 01101110 00010000 - 320 - 01001000 11000000 11000000 11000000 00000111 10000001 11001100 00000001 - 328 - 01111110 00010000 01001000 11000000 11000000 11000000 11000000 00000111 - 336 - 10000010 00101110 01000000 10001110 00010000 01001000 11000000 11000000 - 344 - 11000000 11000000 11000000 00000111 10000000 10100000 00010000 00001110 - 352 - 00010000 01001100 11000100 11001110 11000110 11000001 11000000 00000111 - 360 - 10000100 01101001 11111111 11111110 00010000 01000100 01001010 00100111 - 368 - 00100010 00100100 01001101 11000110 11001100 11001001 11001001 11001001 - 376 - 11000000 00000000 00000000 00001010 11001010 10101010 10100011 11001111 +open wave file 'HelloWorld1 xroar.wav'... +Framerate: 22050 +Number of audio frames: 75025 +channels: 1 +samplewidth: 1 (8Bit wave file) +Use trigger value: 51.0 +First bit is at: 15 +enable half sinus scan +sync diff info: 32 vs. 5 +No sync needed. +disable half sinus scan + 0 - 10101100 10110100 11010110 01011010 01101010 01101100 10101100 10110100 + 8 - 11010110 01011010 01101010 01101100 10101100 10110100 11010110 01011010 + 16 - 01101010 01101100 10101100 10110100 11010110 01011010 01101010 01101100 + 24 - 10101100 10110100 11010110 01011010 01101010 01101100 10101100 10110100 + 32 - 11010110 01011010 01101010 01101100 10101100 10110100 11010110 01011010 + 40 - 01101010 01101100 10101100 10110100 11010110 01011010 01101010 01101100 + 48 - 10101100 10110100 11010110 01011010 01101010 01101100 10101100 10110100 + 56 - 11010110 11011010 01101010 01101100 10101100 10110100 11010110 11011010 + 64 - 01101010 01101100 10101100 10110100 11010110 11011010 01101010 01101100 + 72 - 10101100 10110100 11010110 11011010 01101010 01101100 10101100 10110100 + 80 - 11010110 11011010 01101010 01101100 10101100 10110100 11010110 11011010 + 88 - 01101010 01101100 10101100 10110100 11010110 11011010 01101010 01101100 + 96 - 10101100 10110100 11010110 11011010 01101010 01101100 10101100 10110100 + 104 - 11010110 11011010 01101010 01101100 10101100 10110100 11010110 11011010 + 112 - 01101010 01101100 10101100 10110100 11010110 11011010 01011010 01101100 + 120 - 10101100 10110100 11010110 11011010 01011010 01101100 10101100 10110100 + 128 - 11010110 11011010 01011010 01101100 10101100 10110100 11010110 11011010 + 136 - 01011010 01101100 10101100 10110100 11010110 11011010 01011010 01101100 + 144 - 10101100 10110100 11010110 11011010 01011010 01101100 10101100 10110100 + 152 - 11010110 11011010 01011010 01101100 10101100 10110100 11010110 11011010 + 160 - 01011010 01101100 10101100 10110100 11010110 11011010 01011010 01101100 + 168 - 10101100 10110100 11010110 11011010 01011010 01101100 10101100 10110100 + 176 - 11010110 11011010 01011010 01101010 10101100 10110100 11010110 11011010 + 184 - 01011010 01101010 10101100 10110100 11010110 11011010 01011010 01101010 + 192 - 10101100 10110100 11010110 11011010 01011010 01101010 10101100 10110100 + 200 - 11010110 11011010 01011010 01101010 10101100 10110100 11010110 11011010 + 208 - 01011010 01101010 10101100 10110100 11010110 11011010 01011010 01101010 + 216 - 10101100 10110100 11010110 11011010 01011010 01101010 10101100 10110100 + 224 - 11010110 11011010 01011010 01101010 10101100 10110100 11010110 11011010 + 232 - 01011010 01101010 10101100 10110100 11010110 11011010 01011010 01101010 + 240 - 10101100 10110100 11010110 11011010 01011010 01101010 10101100 10110100 + 248 - 11010110 11011010 01011010 01101010 10101100 10110100 11010110 01101100 + 256 - 01111000 00000000 11100000 00000100 00000100 00000100 00001100 00001000 + 264 - 00001000 00000100 00000100 00000000 00000000 00000000 00000000 00000000 + 272 - 00000000 00000000 11110000 11010110 10110100 10110110 11010110 01011010 + 280 - 01101010 10101100 10110100 10110110 11010110 01011010 01101010 10101100 + 288 - 10110100 10110110 11010110 01011010 01101010 10101100 10110100 10110110 + 296 - 11010110 01011010 01101010 10101100 10110100 10110110 11010110 01011010 + 304 - 01101010 10101100 10110100 10110110 11010110 01011010 01101010 10101100 + 312 - 10110100 10110110 11010110 01011010 01101010 10101100 10110100 10110110 + 320 - 11010110 01011010 01101010 10101100 10110100 10110110 11010110 01011010 + 328 - 01101010 10101100 10110100 10110110 11010110 01011010 01101010 10101100 + 336 - 10110100 10110110 11010110 01011010 01101010 10101100 10110100 10110110 + 344 - 11010110 01011010 01101010 10101100 10110100 10110110 11010110 01011010 + 352 - 01101010 10101100 10110100 10110110 11010110 01011010 01101010 10101100 + 360 - 10110100 10110110 11010110 01011010 01101010 10101100 10110100 10110110 + 368 - 11010110 01011010 01101010 10101100 10110100 10110110 11010110 01011010 + 376 - 01101010 01101100 10110100 10110110 11010110 01011010 01101010 01101100 + 384 - 10110100 10110110 11010110 01011010 01101010 01101100 10110100 10110110 + 392 - 11010110 01011010 01101010 01101100 10110100 10110110 11010110 01011010 + 400 - 01101010 01101100 10110100 10110110 11010110 01011010 01101010 01101100 + 408 - 10110100 10110110 11010110 01011010 01101010 01101100 10110100 10110110 + 416 - 11010110 01011010 01101010 01101100 10110100 10110110 11010110 01011010 + 424 - 01101010 01101100 10110100 10110110 11010110 01011010 01101010 01101100 + 432 - 10110100 10110110 11010110 01011010 01101010 01101100 10101100 10110110 + 440 - 11010110 01011010 01101010 01101100 10101100 10110110 11010110 01011010 + 448 - 01101010 01101100 10101100 10110110 11010110 01011010 01101010 01101100 + 456 - 10101100 10110110 11010110 01011010 01101010 01101100 10101100 10110110 + 464 - 11010110 01011010 01101010 01101100 10101100 10110110 11010110 01011010 + 472 - 01101010 01101100 10101100 10110110 11010110 01011010 01101010 01101100 + 480 - 10101100 10110110 11010110 01011010 01101010 01101100 10101100 10110110 + 488 - 11010110 01011010 01101010 01101100 10101100 10110110 11010110 01011010 + 496 - 01101010 01101100 10101100 10110100 11010110 01011010 01101010 01101100 + 504 - 10101100 10110100 11010110 01011010 01101010 01101100 10101100 10110100 + 512 - 11010110 01011010 01101010 01101100 10101100 10110100 11010110 01011010 + 520 - 01101010 01101100 10101100 10110100 11010110 01011010 01101010 01101100 + 528 - 10101100 10110100 11010110 01011010 01011010 01111100 00000000 10011100 + 536 - 01111000 11001000 00000000 10110000 00000010 00001000 00100110 00001100 + 544 - 11010110 00001000 10011100 00000100 00111100 00000100 10001000 00011000 + 552 - 00000000 01111000 10011000 00000000 01101000 11000010 00001100 10010010 + 560 - 10111100 10001000 00100100 01100010 00100100 01110010 11110010 00000100 + 568 - 11011010 11110010 01010100 01110010 00100100 00001000 10001000 00000000 + 576 - 01111000 10001000 00000000 01111000 11100010 00001000 00100110 00000000 + 584 - 00000000 00000000 11110100 11010110 11011010 00111100 11111110 00000000 + +4752 Bits: 2401 positive bits and 2351 negative bits + +Bit 1: 1470-2205Hz avg: 1487.6Hz variation: 735Hz +Bit 0: 689-1378Hz avg: 1332.3Hz variation: 689Hz """ From aeb89fcd1d12c3e4c78e145541c98549179b13d2 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Thu, 22 Aug 2013 19:08:23 +0200 Subject: [PATCH 027/151] current broken state --- PyDC/wave2bitstream.py | 167 +++++++++++++++++++++++++++++------------ 1 file changed, 120 insertions(+), 47 deletions(-) diff --git a/PyDC/wave2bitstream.py b/PyDC/wave2bitstream.py index 8b857320..c538d3f1 100644 --- a/PyDC/wave2bitstream.py +++ b/PyDC/wave2bitstream.py @@ -16,14 +16,6 @@ from utils import average, diff_info, print_bitlist -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 ? - -LEAD_IN_PATTERN = "10101010" # 0x55 - - - class Wave2Bitstream(object): WAVE_READ_SIZE = 16 * 1024 # How many frames should be read from WAVE file at once? @@ -40,12 +32,8 @@ class Wave2Bitstream(object): 4: 2147483647, # 32-bit wave file } - def __init__(self, wave_filename, lead_in_pattern, mid_volume_ratio=0.2, hysteresis_ratio=0.9): + def __init__(self, wave_filename, bit_nul_hz, bit_one_hz, hz_variation, mid_volume_ratio=0.1, hysteresis_ratio=0.1): self.wave_filename = wave_filename - self.lead_in_pattern = lead_in_pattern - self.mid_volume_ratio = mid_volume_ratio - self.hysteresis_ratio = hysteresis_ratio - print "open wave file '%s'..." % wave_filename self.wavefile = wave.open(wave_filename, "rb") @@ -63,11 +51,41 @@ def __init__(self, wave_filename, lead_in_pattern, mid_volume_ratio=0.2, hystere self.samplewidth = self.wavefile.getsampwidth() # 1 for 8-bit, 2 for 16-bit, 4 for 32-bit samples print "samplewidth: %i (%sBit wave file)" % (self.samplewidth, self.samplewidth * 8) + # build hysteresis min/max values: - self.trigger_value = self.MAX_VALUES[self.samplewidth] * self.mid_volume_ratio + self.max_value = self.MAX_VALUES[self.samplewidth] + print "the max volume value is:", self.max_value + self.trigger_value = int(round(self.max_value * mid_volume_ratio)) print "Use trigger value:", self.trigger_value - self.half_sinus = False + hysteresis_shift = self.trigger_value * hysteresis_ratio + print "Hysteresis shift:", hysteresis_shift + + self.hysteresis_min = self.trigger_value - hysteresis_shift + assert self.hysteresis_min > 0, "hysteresis ratio to big! (min: %s)" % self.hysteresis_min + + self.hysteresis_max = self.trigger_value + hysteresis_shift + assert self.hysteresis_max < self.max_value, "hysteresis ratio to big! (max: %s)" % self.hysteresis_max + + print "hysteresis trigger valume: %i - %i" % (self.hysteresis_min, self.hysteresis_max) + + + # build min/max Hz values + self.bit_nul_min_hz = bit_nul_hz - hz_variation + self.bit_nul_max_hz = bit_nul_hz + hz_variation + + self.bit_one_min_hz = bit_one_hz - hz_variation + self.bit_one_max_hz = bit_one_hz + hz_variation + print "bit-0 in %sHz - %sHz | bit-1 in %sHz - %sHz" % ( + self.bit_nul_min_hz, self.bit_nul_max_hz, + self.bit_one_min_hz, self.bit_one_max_hz, + ) + assert self.bit_nul_max_hz < self.bit_one_min_hz, "hz variation %sHz to big!" % ( + ((self.bit_nul_max_hz - self.bit_one_min_hz) / 2) + 1 + ) + + self.half_sinus = False # in trigger yield the full cycle + # create the generator chain: @@ -76,6 +94,7 @@ def __init__(self, wave_filename, lead_in_pattern, mid_volume_ratio=0.2, hystere # triggered frame numbers of a half sinus cycle self.iter_trigger_generator = self.iter_trigger(self.wave_values_generator) +# self.iter_trigger_generator = self.iter_simple_trigger(self.wave_values_generator) # duration of a complete sinus cycle self.iter_duration_generator = self.iter_duration(self.iter_trigger_generator) @@ -88,7 +107,11 @@ def sync(self, length): synchronized weave sync trigger """ # go in wave stream to the first bit - first_bit_frame_no, first_bit = self.next() + try: + first_bit_frame_no, first_bit = self.next() + except StopIteration: + print "Error: no bits identified!" + sys.exit(-1) print "First bit is at:", first_bit_frame_no print "enable half sinus scan" @@ -110,9 +133,10 @@ def sync(self, length): diff1, diff2 = diff_info(test_durations) print "sync diff info: %i vs. %i" % (diff1, diff2) - if diff1 < diff2: + if diff1 > diff2: print "Sync one step." self.iter_trigger_generator.next() + print "Synced." else: print "No sync needed." @@ -125,16 +149,12 @@ def __iter__(self): def next(self): return self.iter_bitstream_generator.next() - def iter_bitstream(self, iter_func): + def iter_bitstream(self, iter_duration_generator): """ iterate over self.iter_trigger() and yield the bits """ - bit_one_min_hz = BIT_ONE_HZ - MAX_HZ_VARIATION - bit_one_max_hz = BIT_ONE_HZ + MAX_HZ_VARIATION - - bit_nul_min_hz = BIT_NUL_HZ - MAX_HZ_VARIATION - bit_nul_max_hz = BIT_NUL_HZ + MAX_HZ_VARIATION + assert self.half_sinus == False # Allways trigger full sinus cycle one_hz_count = 0 one_hz_min = sys.maxint @@ -147,12 +167,13 @@ def iter_bitstream(self, iter_func): bit_count = 0 - for frame_no, duration in iter_func: - hz = self.framerate / duration -# print "%sHz" % hz + for frame_no, duration in iter_duration_generator: +# if frame_no > 500: +# sys.exit() - if hz > bit_one_min_hz and hz < bit_one_max_hz: -# print "bit 1" + hz = self.framerate / duration + if hz > self.bit_one_min_hz and hz < self.bit_one_max_hz: + print "bit 1 at %s in %sSamples = %sHz" % (frame_no, duration, hz) bit_count += 1 yield (frame_no, 1) one_hz_count += 1 @@ -161,8 +182,8 @@ def iter_bitstream(self, iter_func): if hz > one_hz_max: one_hz_max = hz one_hz_avg = average(one_hz_avg, hz, one_hz_count) - elif hz > bit_nul_min_hz and hz < bit_nul_max_hz: -# print "bit 0" + elif hz > self.bit_nul_min_hz and hz < self.bit_nul_max_hz: + print "bit 0 at %s in %sSamples = %sHz" % (frame_no, duration, hz) bit_count += 1 yield (frame_no, 0) nul_hz_count += 1 @@ -172,7 +193,7 @@ def iter_bitstream(self, iter_func): nul_hz_max = hz nul_hz_avg = average(nul_hz_avg, hz, nul_hz_count) else: - print "Skip signal with %sHz." % hz + print "Skip signal at %s with %sSamples = %sHz" % (frame_no, duration, hz) continue print @@ -180,12 +201,13 @@ def iter_bitstream(self, iter_func): bit_count, one_hz_count, nul_hz_count ) print - print "Bit 1: %s-%sHz avg: %.1fHz variation: %sHz" % ( - one_hz_min, one_hz_max, one_hz_avg, one_hz_max - one_hz_min - ) - print "Bit 0: %s-%sHz avg: %.1fHz variation: %sHz" % ( - nul_hz_min, nul_hz_max, nul_hz_avg, nul_hz_max - nul_hz_min - ) + if bit_count > 0: + print "Bit 0: %sHz - %sHz avg: %.1fHz variation: %sHz" % ( + nul_hz_min, nul_hz_max, nul_hz_avg, nul_hz_max - nul_hz_min + ) + print "Bit 1: %sHz - %sHz avg: %.1fHz variation: %sHz" % ( + one_hz_min, one_hz_max, one_hz_avg, one_hz_max - one_hz_min + ) def iter_duration(self, iter_trigger): @@ -198,7 +220,8 @@ def iter_duration(self, iter_trigger): yield (frame_no, duration) old_frame_no = frame_no - def iter_trigger(self, iter_wave_values): + + def iter_simple_trigger(self, iter_wave_values): """ yield only the triggered frame numbers simmilar to a Schmitt trigger @@ -206,15 +229,56 @@ def iter_trigger(self, iter_wave_values): last_state = False for frame_no, value in iter_wave_values: if last_state == False and value > self.trigger_value: -# print "half:", self.half_sinus + # print "half:", self.half_sinus yield frame_no last_state = True elif last_state == True and value < -self.trigger_value: if self.half_sinus: -# print "half sinus!" + # print "half sinus!" yield frame_no last_state = False + def iter_trigger(self, iter_wave_values): + """ + yield only the triggered frame numbers + simmilar to a Schmitt trigger + """ + phase = 1 + frame_no1 = 0 + frame_no2 = 0 + frame_no3 = 0 + frame_no4 = 0 + for frame_no, value in iter_wave_values: + print "frame no: %s, phase: %s, volume: %s" % (frame_no, phase, value) +# if frame_no > 100: +# sys.exit() + if phase == 1 and value > self.hysteresis_max: + # go into positive sinus cycle + print "Phase complete in %i,%i,%i,%i" % ( + frame_no - frame_no4, + frame_no2 - frame_no1, + frame_no3 - frame_no2, + frame_no4 - frame_no3, + ) + frame_no1 = frame_no + yield frame_no + phase = 2 + elif phase == 2 and value < self.hysteresis_min: + # leave positive sinus cycle + frame_no2 = frame_no + phase = 3 + elif phase == 3 and value < -self.hysteresis_max: + # go into netative sinus cycle + frame_no3 = frame_no + if self.half_sinus: + print "yield half sinus" + yield frame_no + phase = 4 + elif phase == 4 and value > -self.hysteresis_min: + # leave netative sinus cycle + frame_no4 = frame_no + phase = 1 + def iter_wave_values(self): """ yield frame numer + volume value from the WAVE file @@ -232,6 +296,7 @@ def iter_wave_values(self): get_wave_block_func = functools.partial(self.wavefile.readframes, self.WAVE_READ_SIZE) for frames in iter(get_wave_block_func, ""): for value in array.array(typecode, frames): + print " " * int(round((abs(float(value) / self.max_value * 40) + 20))), "*" frame_no += 1 yield frame_no, value @@ -245,18 +310,26 @@ def iter_wave_values(self): # sys.exit() # FILENAME = "HelloWorld1 xroar.wav" # 8Bit 22050Hz - FILENAME = "HelloWorld1 origin.wav" # 109922 frames, 16Bit wave, 44100Hz -# FILENAME = "LineNumber Test 01.wav" # tokenized BASIC - - - st = Wave2Bitstream(FILENAME, LEAD_IN_PATTERN) +# FILENAME = "HelloWorld1 origin.wav" # 109922 frames, 16Bit wave, 44100Hz + FILENAME = "LineNumber Test 01.wav" # tokenized BASIC + + st = Wave2Bitstream(FILENAME, + bit_nul_hz=1200, # "0" is a single cycle at 1200 Hz + bit_one_hz=2400, # "1" is a single cycle at 2400 Hz + hz_variation=450, # How much Hz can signal scatter to match 1 or 0 bit ? + mid_volume_ratio=0.1, hysteresis_ratio=0.1 + ) bitstream = iter(st) - bitstream.sync(16) bitstream = itertools.imap(lambda x: x[1], bitstream) - print_bitlist(bitstream) + bit_list = array.array('B', bitstream) + + print "-"*79 + print_bitlist(bit_list) + print "-"*79 + print "-- END --" From 9957a05d87341ba8c52837aa37cb3b1c84c3b0a7 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Fri, 23 Aug 2013 11:23:57 +0200 Subject: [PATCH 028/151] Use a 'text level meter' for better debugging --- PyDC/utils.py | 58 ++++++++++++++++++++++++++++++++++ PyDC/wave2bitstream.py | 70 ++++++++++++++++++++++++++++++++++++------ 2 files changed, 119 insertions(+), 9 deletions(-) diff --git a/PyDC/utils.py b/PyDC/utils.py index 505594a7..f1772dc9 100644 --- a/PyDC/utils.py +++ b/PyDC/utils.py @@ -291,6 +291,64 @@ def print_bitlist(bit_list): print_block_bit_list(block_bit_list) +class TextLevelMeter(object): + """ + >>> tl = TextLevelMeter(255, 9) + >>> tl.feed(0) + '| * |' + >>> tl.feed(128) + '| | * |' + >>> tl.feed(255) + '| | *|' + >>> tl.feed(-128) + '| * | |' + >>> tl.feed(-255) + '|* | |' + + >>> tl = TextLevelMeter(255, 74) + >>> tl.feed(0) + '| * |' + >>> tl.feed(128) + '| | * |' + >>> tl.feed(255) + '| | *|' + >>> tl.feed(-128) + '| * | |' + >>> tl.feed(-255) + '|* | |' + """ + def __init__(self, max_value, width): + self.max_value = max_value + + fill_len = int(round(((width - 3) / 2))) + fill = " " * fill_len + self.source_msg = "|" + fill + "|" + fill + "|" + + self.offset = fill_len + 1 + self.max_width = fill_len + + def feed(self, value): + value = int(round( + float(value) / self.max_value * self.max_width + self.offset + )) + return self.source_msg[:value] + "*" + self.source_msg[value + 1:] + + if __name__ == "__main__": import doctest print doctest.testmod() + + + import math + + count = 32 + max_value = 255 + width = 79 + + tl = TextLevelMeter(max_value, width) + for index in xrange(0, count + 1): + angle = 360.0 / count * index + y = math.sin(math.radians(angle)) * max_value + y = round(y) + print tl.feed(y) + # print "%i - %.1f° %i" % (index, angle, y) diff --git a/PyDC/wave2bitstream.py b/PyDC/wave2bitstream.py index c538d3f1..11d70a98 100644 --- a/PyDC/wave2bitstream.py +++ b/PyDC/wave2bitstream.py @@ -11,9 +11,36 @@ import functools import array import itertools +import logging # own modules -from utils import average, diff_info, print_bitlist +from utils import average, diff_info, print_bitlist, TextLevelMeter + + +log = logging.getLogger("PyDC") + +LOG_FORMATTER = logging.Formatter("") # %(asctime)s %(message)s") +LOG_LEVEL_DICT = { + 0: logging.ERROR, + 1: logging.WARNING, + 2: logging.INFO, + 3: logging.DEBUG +} + +log_level = LOG_LEVEL_DICT[3] # args.verbosity +log.setLevel(log_level) + +logfilename = "PyDC.log" # args.logfile +if logfilename: + handler = logging.FileHandler(logfilename, encoding="utf8") + handler.setFormatter(LOG_FORMATTER) + log.addHandler(handler) + +# if args.stdout_log: +handler = logging.StreamHandler() +handler.setFormatter(LOG_FORMATTER) +log.addHandler(handler) + class Wave2Bitstream(object): @@ -32,7 +59,14 @@ class Wave2Bitstream(object): 4: 2147483647, # 32-bit wave file } - def __init__(self, wave_filename, bit_nul_hz, bit_one_hz, hz_variation, mid_volume_ratio=0.1, hysteresis_ratio=0.1): + def __init__(self, wave_filename, + bit_nul_hz, # sinus cycle frequency in Hz for one "0" bit + bit_one_hz, # sinus cycle frequency in Hz for one "1" bit + hz_variation, # How much Hz can signal scatter to match 1 or 0 bit ? + min_volume_ratio=0.1, # Ignore sample frames if lower volume + mid_volume_ratio=0.2, + hysteresis_ratio=0.1 + ): self.wave_filename = wave_filename print "open wave file '%s'..." % wave_filename @@ -55,6 +89,10 @@ def __init__(self, wave_filename, bit_nul_hz, bit_one_hz, hz_variation, mid_volu # build hysteresis min/max values: self.max_value = self.MAX_VALUES[self.samplewidth] print "the max volume value is:", self.max_value + + self.min_volume = int(round(self.max_value * min_volume_ratio)) + print "Ignore sample lower than:", self.min_volume + self.trigger_value = int(round(self.max_value * mid_volume_ratio)) print "Use trigger value:", self.trigger_value @@ -173,7 +211,9 @@ def iter_bitstream(self, iter_duration_generator): hz = self.framerate / duration if hz > self.bit_one_min_hz and hz < self.bit_one_max_hz: - print "bit 1 at %s in %sSamples = %sHz" % (frame_no, duration, hz) + log.debug( + "bit 1 at %s in %sSamples = %sHz" % (frame_no, duration, hz) + ) bit_count += 1 yield (frame_no, 1) one_hz_count += 1 @@ -183,7 +223,9 @@ def iter_bitstream(self, iter_duration_generator): one_hz_max = hz one_hz_avg = average(one_hz_avg, hz, one_hz_count) elif hz > self.bit_nul_min_hz and hz < self.bit_nul_max_hz: - print "bit 0 at %s in %sSamples = %sHz" % (frame_no, duration, hz) + log.debug( + "bit 0 at %s in %sSamples = %sHz" % (frame_no, duration, hz) + ) bit_count += 1 yield (frame_no, 0) nul_hz_count += 1 @@ -249,12 +291,12 @@ def iter_trigger(self, iter_wave_values): frame_no3 = 0 frame_no4 = 0 for frame_no, value in iter_wave_values: - print "frame no: %s, phase: %s, volume: %s" % (frame_no, phase, value) +# print "frame no: %s, phase: %s, volume: %s" % (frame_no, phase, value) # if frame_no > 100: # sys.exit() if phase == 1 and value > self.hysteresis_max: # go into positive sinus cycle - print "Phase complete in %i,%i,%i,%i" % ( + print " ===== Phase complete in %i,%i,%i,%i ===============" % ( frame_no - frame_no4, frame_no2 - frame_no1, frame_no3 - frame_no2, @@ -271,7 +313,7 @@ def iter_trigger(self, iter_wave_values): # go into netative sinus cycle frame_no3 = frame_no if self.half_sinus: - print "yield half sinus" + print " ---- yield half sinus ----------------------------" yield frame_no phase = 4 elif phase == 4 and value > -self.hysteresis_min: @@ -292,11 +334,20 @@ def iter_wave_values(self): ) ) + tlm = TextLevelMeter(self.max_value, 79) + frame_no = 0 get_wave_block_func = functools.partial(self.wavefile.readframes, self.WAVE_READ_SIZE) for frames in iter(get_wave_block_func, ""): for value in array.array(typecode, frames): - print " " * int(round((abs(float(value) / self.max_value * 40) + 20))), "*" + + if abs(value) < self.min_volume: + # Ignore to lower amplitude + continue + + msg = tlm.feed(value) + log.debug(msg) + frame_no += 1 yield frame_no, value @@ -317,7 +368,8 @@ def iter_wave_values(self): bit_nul_hz=1200, # "0" is a single cycle at 1200 Hz bit_one_hz=2400, # "1" is a single cycle at 2400 Hz hz_variation=450, # How much Hz can signal scatter to match 1 or 0 bit ? - mid_volume_ratio=0.1, hysteresis_ratio=0.1 + min_volume_ratio=0.1, # Ignore sample frames if lower volume + mid_volume_ratio=0.2, hysteresis_ratio=0.1 ) bitstream = iter(st) From ab7777cbec69a7473ad8e3b70ed005a4f2e41e38 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Fri, 23 Aug 2013 14:43:02 +0200 Subject: [PATCH 029/151] current state with "auto sync" --- PyDC/PyDC.py | 176 +++++----------------------- PyDC/utils.py | 75 ++++++++++++ PyDC/wave2bitstream.py | 256 +++++++++++++++++++++++++++-------------- 3 files changed, 270 insertions(+), 237 deletions(-) diff --git a/PyDC/PyDC.py b/PyDC/PyDC.py index 9007f137..1e8b232b 100755 --- a/PyDC/PyDC.py +++ b/PyDC/PyDC.py @@ -1,4 +1,5 @@ #!/usr/bin/env python2 +# coding: utf-8 """ Convert dragon 32 Cassetts WAV files into plain text. @@ -25,6 +26,7 @@ import array import functools import itertools +from wave2bitstream import Wave2Bitstream try: import audioop @@ -34,7 +36,8 @@ audioop = None # own modules -from utils import ProcessInfo, human_duration, average +from utils import ProcessInfo, human_duration, average, print_bitlist, \ + find_iter_window, list2str, count_continuous_pattern from basic_tokens import BASIC_TOKENS, FUNCTION_TOKEN @@ -42,7 +45,7 @@ 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 ? -LEADER_BYTE = "10101010" # 0x55 +LEAD_IN_PATTERN = "10101010" # 0x55 SYNC_BYTE = "00111100" # 0x3C # Block types: @@ -341,69 +344,6 @@ def _print_status(frame_no, framerate): ) -def count_continuous_pattern(bits, pattern): - """ - count 'pattern' matches without ceasing. - - >>> bit_str = ( - ... "00111100" - ... "00111100" - ... "0101") - >>> pos = count_continuous_pattern([int(i) for i in bit_str], "00111100") - >>> bit_str[pos*8:] - '0101' - >>> pos - 2 - - >>> count_continuous_pattern([1,1,1,2,3], "1") - 3 - - >>> count_continuous_pattern([1,2,3], "99") - 0 - - >>> count_continuous_pattern([0,1,0,1], "01") - 2 - """ - pattern_len = len(pattern) - pattern = [int(i) for i in pattern] - for count, data in enumerate(iter_steps(bits, pattern_len), 1): - if data != pattern: - count -= 1 - break - return count - - -def find_iter_window(bit_list, pattern): - """ - Search for 'pattern' in bit-by-bit steps (iter window) - and return the number of bits before the 'pattern' match. - - Useable for slicing all bits before the first 'pattern' match: - - >>> bit_str = "111010111" - >>> pos = find_iter_window([int(i) for i in bit_str], "010") - >>> bit_str[pos:] - '010111' - >>> pos - 3 - - >>> find_iter_window([1,1,1], "0") - 0 - >>> find_iter_window([1,0,0], "1") - 0 - >>> find_iter_window([0,1,0], "1") - 1 - >>> find_iter_window([0,0,1], "1") - 2 - """ - pattern_len = len(pattern) - pattern = [int(i) for i in pattern] - for pos, data in enumerate(iter_window(bit_list, pattern_len)): - if data == pattern: - return pos - return 0 - - def pop_bytes_from_bit_list(bit_list, count): """ >>> bit_str = ( @@ -431,33 +371,6 @@ def pop_bytes_from_bit_list(bit_list, count): return bit_list, data -def print_block_bit_list(block_bit_list): - in_line_count = 0 - - line = "" - for no, block in enumerate(block_bit_list, -DISPLAY_BLOCK_COUNT + 1): - line += "%s " % list2str(block) - in_line_count += 1 - if in_line_count >= DISPLAY_BLOCK_COUNT: - in_line_count = 0 - print "%4s - %s" % (no, line) - line = "" - if in_line_count > 0: - print - -def print_bitlist(bit_list): - block_bit_list = iter_steps(bit_list, steps=8) - print_block_bit_list(block_bit_list) - - -def list2str(l): - """ - >>> list2str([0, 0, 0, 1, 0, 0, 1, 0]) - '00010010' - """ - return "".join([str(c) for c in l]) - - def bits2byte_no(bits): """ >>> c = bits2byte_no([0, 0, 0, 1, 0, 0, 1, 0]) @@ -539,15 +452,15 @@ def print_as_hex_list(block_bit_list): def get_block_info(bit_list): # Searching for lead-in byte - leader_pos = find_iter_window(bit_list, LEADER_BYTE) # Search for LEADER_BYTE in bit-by-bit steps - print "Start leader '%s' found at position: %i" % (LEADER_BYTE, leader_pos) + leader_pos = find_iter_window(bit_list, LEAD_IN_PATTERN) # Search for LEAD_IN_PATTERN in bit-by-bit steps + print "Start leader '%s' found at position: %i" % (LEAD_IN_PATTERN, leader_pos) # Cut bits before the first 01010101 start leader print "bits before header:", repr(list2str(bit_list[:leader_pos])) bit_list = bit_list[leader_pos:] # count lead-in byte matches without ceasing to get faster to the sync-byte - leader_count = count_continuous_pattern(bit_list, LEADER_BYTE) + leader_count = count_continuous_pattern(bit_list, LEAD_IN_PATTERN) print "Found %i leader bytes" % leader_count if leader_count == 0: print "WARNING: leader bytes not found! Maybe 'even_odd' bool wrong???" @@ -906,33 +819,20 @@ def print_bit_list_stats(bit_list): # created by Xroar Emulator -# FILENAME = "HelloWorld1 xroar.wav" # 8Bit 22050Hz - -# even_odd = False # correct: + FILENAME = "HelloWorld1 xroar.wav" # 8Bit 22050Hz # Bit 1 min: 1696Hz avg: 2058.3Hz max: 2205Hz variation: 509Hz # Bit 0 min: 595Hz avg: 1090.4Hz max: 1160Hz Variation: 565Hz # 4760 Bits: 2243 positive bits and 2517 negative bits -# even_odd = True # wrong: -# Bit 1 min: 1470Hz avg: 1487.5Hz max: 2205Hz variation: 735Hz -# Bit 0 min: 689Hz avg: 1332.3Hz max: 1378Hz Variation: 689Hz -# 4760 Bits: 2404 positive bits and 2356 negative bits - # created by origin Dragon 32 machine # 16Bit 44.1KHz mono # FILENAME = "HelloWorld1 origin.wav" # 109922 frames, 2735 bits (raw) -# even_odd = True # correct: # Bit 1 min: 1764Hz avg: 2013.9Hz max: 2100Hz variation: 336Hz # Bit 0 min: 595Hz avg: 1090.2Hz max: 1336Hz Variation: 741Hz # 2710 Bits: 1217 positive bits and 1493 negative bits -# even_odd = False # wrong: - # Bit 1 min: 1422Hz avg: 1461.5Hz max: 2100Hz variation: 678Hz - # Bit 0 min: 459Hz avg: 1265.1Hz max: 1378Hz Variation: 919Hz - # 2712 Bits: 1723 positive bits and 989 negative bits - """ @@ -949,62 +849,38 @@ def print_bit_list_stats(bit_list): # Test files from: - # http://archive.worldofdragon.org/archive/index.php?dir=Tapes/Dragon/wav/ + # http://archive.worldofdragon.org/archive/index.php?dir=Tapes/Dragon/wav/ # FILENAME = "Quickbeam Software - Duplicas v3.0.wav" # binary! -# even_odd = False # FILENAME = "Dragon Data Ltd - Examples from the Manual - 39~58 [run].wav" -# even_odd = False # correct: # Bit 1 min: 1696Hz avg: 2004.0Hz max: 2004Hz variation: 308Hz # Bit 0 min: 1025Hz avg: 1025.0Hz max: 1025Hz Variation: 0Hz # 155839 Bits: 73776 positive bits and 82063 negative bits -# even_odd = True # wrong: - # Bit 1 min: 2004Hz avg: 2004.5Hz max: 2940Hz variation: 936Hz - # Bit 0 min: 1025Hz avg: 1330.1Hz max: 1378Hz Variation: 353Hz - # 155840 Bits: 4018 positive bits and 151822 negative bits - - # FILENAME = "1_MANIA.WAV" # 148579 frames, 4879 bits (raw) # FILENAME = "2_DBJ.WAV" # TODO -# even_odd = False # BASIC file with high line numbers: - FILENAME = "LineNumber Test 01.wav" # tokenized BASIC - even_odd = True - +# FILENAME = "LineNumber Test 01.wav" # tokenized BASIC # FILENAME = "LineNumber Test 02.wav" # ASCII BASIC -# even_odd = True - - - print "Read '%s'..." % FILENAME - wavefile = wave.open(FILENAME, "r") - - framerate = wavefile.getframerate() # frames / second - print "Framerate:", framerate - frame_count = wavefile.getnframes() - print "Number of audio frames:", frame_count - nchannels = wavefile.getnchannels() # typically 1 for mono, 2 for stereo - print "channels:", nchannels - 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 - print "samplewidth: %i (%sBit wave file)" % (samplewidth, samplewidth * 8) - - start_time = time.time() - print "Read wave file...", - wave_samples = list(iter_wave_values(wavefile)) - print "DONE in %s" % human_duration(time.time() - start_time) - print "Convert WAVE samples to binary data..." - bit_generator = samples2bits(wave_samples, framerate, frame_count, even_odd) - bit_list = array.array('B', bit_generator) # 1.1sec 17KB/s - print_bit_list_stats(bit_list) - -# print "-"*79 -# print_bitlist(bit_list) -# print "-"*79 + st = Wave2Bitstream(FILENAME, + bit_nul_hz=1200, # "0" is a single cycle at 1200 Hz + bit_one_hz=2400, # "1" is a single cycle at 2400 Hz + hz_variation=450, # How much Hz can signal scatter to match 1 or 0 bit ? +# min_volume_ratio=0.01, # Ignore sample frames if lower volume +# mid_volume_ratio=0.2, hysteresis_ratio=0.1 + ) + bitstream = iter(st) + bitstream.sync(16) + bitstream = itertools.imap(lambda x: x[1], bitstream) + bit_list = array.array('B', bitstream) + + print "-"*79 + print_bitlist(bit_list) + print "-"*79 # print_block_table(bit_list) # print "-"*79 # sys.exit() diff --git a/PyDC/utils.py b/PyDC/utils.py index f1772dc9..56384283 100644 --- a/PyDC/utils.py +++ b/PyDC/utils.py @@ -265,6 +265,81 @@ def get_diff(l): return diff1, diff2 +def iter_pare_sum(data): + """ + >>> def g(data): + ... for i in data: yield i + >>> list(iter_pare_sum(g([5,5,10,10,4,4,10,10,5,5]))) + [20, 8, 20, 10] + + >>> list(iter_pare_sum([5,10,10,4,4,10,10,5,5,10])) + [20, 8, 20, 10] + + >>> list(iter_pare_sum([5,4,10,11,5,4,21,22,2,1])) + [21, 9, 43, 3] + + >>> list(iter_pare_sum([ + ... 5,5,10,10,5,5, + ... 5, # <- resync 1 + ... 8,8,5,5,10,10, + ... 10, # <- resync 2 + ... 7,7,5,5,10,10,2,2,3,3 + ... ])) + [20, 10, 10, 16, 10, 20, 14, 10, 20, 4, 6] + + resync ^ ^ + """ + for previous, current, next_value in itertools.islice(iter_window(data, window_size=3), 1, None, 2): + diff1 = abs(previous - current) + diff2 = abs(current - next_value) + + if diff1 < diff2: + yield previous + current + else: + yield current + next_value + + +def iter_pare_sum2(data): + """ + >>> def g(data): + ... for no, i in enumerate(data): yield (no, i) + + >>> l = [5,5,10,10,4,10,10,5,5] + >>> list(g(l)) + [(0, 5), (1, 5), (2, 10), (3, 10), (4, 4), (5, 4), (6, 10), (7, 10), (8, 5), (9, 5)] + >>> list(iter_pare_sum2(g(l))) + [(2, 20), (4, 8), (6, 20), (8, 10)] + + >>> list(iter_pare_sum2(g([5,10,10,4,4,10,10,5,5,10]))) + [(2, 20), (4, 8), (6, 20), (8, 10)] + + >>> list(iter_pare_sum2(g([5,4,10,11,5,4,21,22,2,1]))) + [(2, 21), (4, 9), (6, 43), (8, 3)] + + >>> list(iter_pare_sum2(g([ + ... 5,5,10,10,5,5, + ... 5, # <- resync 1 + ... 8,8,5,5,10,10, + ... 10, # <- resync 2 + ... 7,7,5,5,10,10,2,2,3,3 + ... ]))) + [(2, 20), (4, 10), (6, 10), (8, 16), (10, 10), (12, 20), (14, 14), (16, 10), (18, 20), (20, 4), (22, 6)] + + + [20, 10, 10, 16, 10, 20, 14, 10, 20, 4, 6] + + resync ^ ^ + """ + for previous, current, next_value in itertools.islice(iter_window(data, window_size=3), 1, None, 2): + diff1 = abs(previous[1] - current[1]) + diff2 = abs(current[1] - next_value[1]) + + if diff1 < diff2: + yield (current[0], previous[1] + current[1]) + else: + yield (current[0], current[1] + next_value[1]) + + def list2str(l): """ >>> list2str([0, 0, 0, 1, 0, 0, 1, 0]) diff --git a/PyDC/wave2bitstream.py b/PyDC/wave2bitstream.py index 11d70a98..a6b97e84 100644 --- a/PyDC/wave2bitstream.py +++ b/PyDC/wave2bitstream.py @@ -14,7 +14,10 @@ import logging # own modules -from utils import average, diff_info, print_bitlist, TextLevelMeter +from utils import average, diff_info, print_bitlist, TextLevelMeter, iter_window, \ + human_duration, ProcessInfo +import struct +import time log = logging.getLogger("PyDC") @@ -27,21 +30,6 @@ 3: logging.DEBUG } -log_level = LOG_LEVEL_DICT[3] # args.verbosity -log.setLevel(log_level) - -logfilename = "PyDC.log" # args.logfile -if logfilename: - handler = logging.FileHandler(logfilename, encoding="utf8") - handler.setFormatter(LOG_FORMATTER) - log.addHandler(handler) - -# if args.stdout_log: -handler = logging.StreamHandler() -handler.setFormatter(LOG_FORMATTER) -log.addHandler(handler) - - class Wave2Bitstream(object): @@ -63,8 +51,8 @@ def __init__(self, wave_filename, bit_nul_hz, # sinus cycle frequency in Hz for one "0" bit bit_one_hz, # sinus cycle frequency in Hz for one "1" bit hz_variation, # How much Hz can signal scatter to match 1 or 0 bit ? - min_volume_ratio=0.1, # Ignore sample frames if lower volume - mid_volume_ratio=0.2, + min_volume_ratio=1, # Ignore sample frames if lower volume + mid_volume_ratio=5, hysteresis_ratio=0.1 ): self.wave_filename = wave_filename @@ -90,22 +78,22 @@ def __init__(self, wave_filename, self.max_value = self.MAX_VALUES[self.samplewidth] print "the max volume value is:", self.max_value - self.min_volume = int(round(self.max_value * min_volume_ratio)) - print "Ignore sample lower than:", self.min_volume - - self.trigger_value = int(round(self.max_value * mid_volume_ratio)) - print "Use trigger value:", self.trigger_value - - hysteresis_shift = self.trigger_value * hysteresis_ratio - print "Hysteresis shift:", hysteresis_shift + self.min_volume = int(round(self.max_value * min_volume_ratio / 100)) + print "Ignore sample lower than %.f%% = %i" % (min_volume_ratio, self.min_volume) - self.hysteresis_min = self.trigger_value - hysteresis_shift - assert self.hysteresis_min > 0, "hysteresis ratio to big! (min: %s)" % self.hysteresis_min + self.trigger_value = int(round(self.max_value * mid_volume_ratio / 100)) + print "Use trigger value: %.f%% = %i" % (mid_volume_ratio, self.trigger_value) - self.hysteresis_max = self.trigger_value + hysteresis_shift - assert self.hysteresis_max < self.max_value, "hysteresis ratio to big! (max: %s)" % self.hysteresis_max - - print "hysteresis trigger valume: %i - %i" % (self.hysteresis_min, self.hysteresis_max) +# hysteresis_shift = self.trigger_value * hysteresis_ratio +# print "Hysteresis shift:", hysteresis_shift +# +# self.hysteresis_min = self.trigger_value - hysteresis_shift +# assert self.hysteresis_min > 0, "hysteresis ratio to big! (min: %s)" % self.hysteresis_min +# +# self.hysteresis_max = self.trigger_value + hysteresis_shift +# assert self.hysteresis_max < self.max_value, "hysteresis ratio to big! (max: %s)" % self.hysteresis_max +# +# print "hysteresis trigger valume: %i - %i" % (self.hysteresis_min, self.hysteresis_max) # build min/max Hz values @@ -137,13 +125,16 @@ def __init__(self, wave_filename, # duration of a complete sinus cycle self.iter_duration_generator = self.iter_duration(self.iter_trigger_generator) + self.auto_sync_duration_generator = self.auto_sync_duration(self.iter_duration_generator) + # build from sinus cycle duration the bit stream - self.iter_bitstream_generator = self.iter_bitstream(self.iter_duration_generator) + self.iter_bitstream_generator = self.iter_bitstream(self.auto_sync_duration_generator) def sync(self, length): """ synchronized weave sync trigger """ + return # go in wave stream to the first bit try: first_bit_frame_no, first_bit = self.next() @@ -194,6 +185,19 @@ def iter_bitstream(self, iter_duration_generator): """ assert self.half_sinus == False # Allways trigger full sinus cycle + process_info = ProcessInfo(self.frame_count, use_last_rates=4) + start_time = time.time() + next_status = start_time + 0.25 + + def _print_status(frame_no, bit_count): + ms = float(frame_no) / self.framerate + rest, eta, rate = process_info.update(frame_no) + sys.stdout.write( + "\r%i frames (wav pos:%s) get %iBits - eta: %s (rate: %iFrames/sec) " % ( + frame_no, human_duration(ms), bit_count, eta, rate + ) + ) + one_hz_count = 0 one_hz_min = sys.maxint one_hz_avg = None @@ -235,9 +239,18 @@ def iter_bitstream(self, iter_duration_generator): nul_hz_max = hz nul_hz_avg = average(nul_hz_avg, hz, nul_hz_count) else: - print "Skip signal at %s with %sSamples = %sHz" % (frame_no, duration, hz) + log.debug( + "Skip signal at %s with %sSamples = %sHz" % (frame_no, duration, hz) + ) continue + if time.time() > next_status: + next_status = time.time() + 1 + _print_status(frame_no, bit_count) + + _print_status(frame_no, bit_count) + print + print print "%i Bits: %i positive bits and %i negative bits" % ( bit_count, one_hz_count, nul_hz_count @@ -252,6 +265,38 @@ def iter_bitstream(self, iter_duration_generator): ) + def auto_sync_duration(self, iter_duration): + """ + yield the duration of two frames in a row. + """ + for previous, current, next_value in itertools.islice(iter_window(iter_duration, window_size=3), 1, None, 2): + diff1 = abs(previous[1] - current[1]) + diff2 = abs(current[1] - next_value[1]) + + if diff1 < diff2: + log.debug("EVEN") + yield (current[0], previous[1] + current[1]) + else: + log.debug("ODD") + yield (current[0], current[1] + next_value[1]) + + +# old_frame = next(iter_duration) +# print "old frame:", old_frame +# count = 0 +# while True: +# count += 1 +# if count > 40:sys.exit() +# durations = itertools.islice(iter_duration, 3) +# print list(durations) +# diff += abs(no1 - no2) +# +# old_frame_no = next(iter_trigger) +# for frame_no in iter_trigger: +# duration = frame_no - old_frame_no +# yield (frame_no, duration) +# old_frame_no = frame_no + def iter_duration(self, iter_trigger): """ yield the duration of two frames in a row. @@ -262,64 +307,50 @@ def iter_duration(self, iter_trigger): yield (frame_no, duration) old_frame_no = frame_no - - def iter_simple_trigger(self, iter_wave_values): + def iter_trigger(self, iter_wave_values): """ yield only the triggered frame numbers simmilar to a Schmitt trigger """ - last_state = False + last_state = True for frame_no, value in iter_wave_values: if last_state == False and value > self.trigger_value: - # print "half:", self.half_sinus + log.debug(" ==== go into positive sinus cycle ===============") yield frame_no last_state = True elif last_state == True and value < -self.trigger_value: - if self.half_sinus: - # print "half sinus!" - yield frame_no - last_state = False - - def iter_trigger(self, iter_wave_values): - """ - yield only the triggered frame numbers - simmilar to a Schmitt trigger - """ - phase = 1 - frame_no1 = 0 - frame_no2 = 0 - frame_no3 = 0 - frame_no4 = 0 - for frame_no, value in iter_wave_values: -# print "frame no: %s, phase: %s, volume: %s" % (frame_no, phase, value) -# if frame_no > 100: -# sys.exit() - if phase == 1 and value > self.hysteresis_max: - # go into positive sinus cycle - print " ===== Phase complete in %i,%i,%i,%i ===============" % ( - frame_no - frame_no4, - frame_no2 - frame_no1, - frame_no3 - frame_no2, - frame_no4 - frame_no3, - ) - frame_no1 = frame_no + log.debug(" ---- go into netative sinus cycle ---------------") +# if self.half_sinus: + log.debug(" -**- yield half sinus -**-------------------------") yield frame_no - phase = 2 - elif phase == 2 and value < self.hysteresis_min: - # leave positive sinus cycle - frame_no2 = frame_no - phase = 3 - elif phase == 3 and value < -self.hysteresis_max: - # go into netative sinus cycle - frame_no3 = frame_no - if self.half_sinus: - print " ---- yield half sinus ----------------------------" - yield frame_no - phase = 4 - elif phase == 4 and value > -self.hysteresis_min: - # leave netative sinus cycle - frame_no4 = frame_no - phase = 1 + last_state = False +# +# def iter_trigger(self, iter_wave_values): +# """ +# yield only the triggered frame numbers +# simmilar to a Schmitt trigger +# """ +# phase = 1 +# for frame_no, value in iter_wave_values: +# # print "frame no: %s, phase: %s, volume: %s" % (frame_no, phase, value) +# # if frame_no > 100: +# # sys.exit() +# if phase == 1 and value > self.hysteresis_max: +# log.debug(" ==== go into positive sinus cycle ===============") +# yield frame_no +# phase = 2 +# elif phase == 2 and value < self.hysteresis_min: +# log.debug(" ---- leave positive sinus cycle -----------------") +# phase = 3 +# elif phase == 3 and value < -self.hysteresis_max: +# log.debug(" ---- go into netative sinus cycle ---------------") +# if self.half_sinus: +# log.debug(" -**- yield half sinus -**-------------------------") +# yield frame_no +# phase = 4 +# elif phase == 4 and value > -self.hysteresis_min: +# log.debug(" ---- leave netative sinus cycle -----------------") +# phase = 1 def iter_wave_values(self): """ @@ -338,19 +369,52 @@ def iter_wave_values(self): frame_no = 0 get_wave_block_func = functools.partial(self.wavefile.readframes, self.WAVE_READ_SIZE) + skipped_values = 0 for frames in iter(get_wave_block_func, ""): for value in array.array(typecode, frames): if abs(value) < self.min_volume: # Ignore to lower amplitude + skipped_values += 1 continue + elif skipped_values > 0: + log.debug(" *** Have %i samples skipped, because to lower amplitude." % skipped_values) + skipped_values = 0 msg = tlm.feed(value) - log.debug(msg) + log.debug( + "%s value: %i (%.1f%%)" % (msg, value, abs(self.max_value / value)) + ) frame_no += 1 yield frame_no, value +# def iter_wave_valuesOLD(self): +# if self.samplewidth == 1: +# struct_unpack_str = " 1000: break +# value = struct.unpack(struct_unpack_str, frame)[0] +# value = -value +# msg = tlm.feed(value) +# log.debug("%s value: %i" % (msg, value)) +# yield frame_no, value + if __name__ == "__main__": import doctest @@ -361,15 +425,33 @@ def iter_wave_values(self): # sys.exit() # FILENAME = "HelloWorld1 xroar.wav" # 8Bit 22050Hz -# FILENAME = "HelloWorld1 origin.wav" # 109922 frames, 16Bit wave, 44100Hz - FILENAME = "LineNumber Test 01.wav" # tokenized BASIC + FILENAME = "HelloWorld1 origin.wav" # 109922 frames, 16Bit wave, 44100Hz +# FILENAME = "LineNumber Test 01.wav" # tokenized BASIC + + log_level = LOG_LEVEL_DICT[3] # args.verbosity + log.setLevel(log_level) + + logfilename = FILENAME + ".log" # args.logfile + if logfilename: + handler = logging.FileHandler(logfilename, + # mode='a', + mode='w', + encoding="utf8" + ) + handler.setFormatter(LOG_FORMATTER) + log.addHandler(handler) + + # if args.stdout_log: + # handler = logging.StreamHandler() + # handler.setFormatter(LOG_FORMATTER) + # log.addHandler(handler) st = Wave2Bitstream(FILENAME, bit_nul_hz=1200, # "0" is a single cycle at 1200 Hz bit_one_hz=2400, # "1" is a single cycle at 2400 Hz hz_variation=450, # How much Hz can signal scatter to match 1 or 0 bit ? - min_volume_ratio=0.1, # Ignore sample frames if lower volume - mid_volume_ratio=0.2, hysteresis_ratio=0.1 +# min_volume_ratio=0.01, # Ignore sample frames if lower volume +# mid_volume_ratio=0.2, hysteresis_ratio=0.1 ) bitstream = iter(st) From 363aa16f86e047a93ed034c34a0829df852b0086 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Fri, 23 Aug 2013 18:32:05 +0200 Subject: [PATCH 030/151] commit current, broken state --- PyDC/LineNumber Test 02.wav | Bin 0 -> 401452 bytes PyDC/PyDC.py | 41 ++++++++++--- PyDC/utils.py | 114 +++++++++++++----------------------- PyDC/wave2bitstream.py | 79 ++++++++++--------------- 4 files changed, 106 insertions(+), 128 deletions(-) create mode 100755 PyDC/LineNumber Test 02.wav mode change 100644 => 100755 PyDC/utils.py diff --git a/PyDC/LineNumber Test 02.wav b/PyDC/LineNumber Test 02.wav new file mode 100755 index 0000000000000000000000000000000000000000..1405dd4bba06e2299a008b2837dd1fe2f98cd343 GIT binary patch literal 401452 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

7bdD?{a&u6uh2(31XYdFf%+1g24}bM< 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 literal 0 HcmV?d00001 diff --git a/PyDC/PyDC.py b/PyDC/PyDC.py index 1e8b232b..f1806ad1 100755 --- a/PyDC/PyDC.py +++ b/PyDC/PyDC.py @@ -27,6 +27,7 @@ import functools import itertools from wave2bitstream import Wave2Bitstream +import logging try: import audioop @@ -35,9 +36,13 @@ print "Can't use audioop:", err audioop = None +log = logging.getLogger("PyDC") + + # own modules from utils import ProcessInfo, human_duration, average, print_bitlist, \ - find_iter_window, list2str, count_continuous_pattern + find_iter_window, list2str, count_continuous_pattern, LOG_LEVEL_DICT, \ + LOG_FORMATTER from basic_tokens import BASIC_TOKENS, FUNCTION_TOKEN @@ -819,7 +824,7 @@ def print_bit_list_stats(bit_list): # created by Xroar Emulator - FILENAME = "HelloWorld1 xroar.wav" # 8Bit 22050Hz +# FILENAME = "HelloWorld1 xroar.wav" # 8Bit 22050Hz # Bit 1 min: 1696Hz avg: 2058.3Hz max: 2205Hz variation: 509Hz # Bit 0 min: 595Hz avg: 1090.4Hz max: 1160Hz Variation: 565Hz # 4760 Bits: 2243 positive bits and 2517 negative bits @@ -828,7 +833,7 @@ def print_bit_list_stats(bit_list): # created by origin Dragon 32 machine # 16Bit 44.1KHz mono -# FILENAME = "HelloWorld1 origin.wav" # 109922 frames, 2735 bits (raw) +# FILENAME = "HelloWorld1 origin.wav" # no sync neede # Bit 1 min: 1764Hz avg: 2013.9Hz max: 2100Hz variation: 336Hz # Bit 0 min: 595Hz avg: 1090.2Hz max: 1336Hz Variation: 741Hz # 2710 Bits: 1217 positive bits and 1493 negative bits @@ -853,7 +858,7 @@ def print_bit_list_stats(bit_list): # FILENAME = "Quickbeam Software - Duplicas v3.0.wav" # binary! -# FILENAME = "Dragon Data Ltd - Examples from the Manual - 39~58 [run].wav" + FILENAME = "Dragon Data Ltd - Examples from the Manual - 39~58 [run].wav" # Bit 1 min: 1696Hz avg: 2004.0Hz max: 2004Hz variation: 308Hz # Bit 0 min: 1025Hz avg: 1025.0Hz max: 1025Hz Variation: 0Hz # 155839 Bits: 73776 positive bits and 82063 negative bits @@ -862,8 +867,30 @@ def print_bit_list_stats(bit_list): # FILENAME = "2_DBJ.WAV" # TODO # BASIC file with high line numbers: -# FILENAME = "LineNumber Test 01.wav" # tokenized BASIC -# FILENAME = "LineNumber Test 02.wav" # ASCII BASIC +# FILENAME = "LineNumber Test 01.wav" # tokenized BASIC - no sync +# FILENAME = "LineNumber Test 02.wav" # ASCII BASIC - no sync + + + + log_level = LOG_LEVEL_DICT[3] # args.verbosity + log.setLevel(log_level) + + logfilename = FILENAME + ".log" # args.logfile + if logfilename: + print "Log into '%s'" % logfilename + handler = logging.FileHandler(logfilename, + # mode='a', + mode='w', + encoding="utf8" + ) + handler.setFormatter(LOG_FORMATTER) + log.addHandler(handler) + + # if args.stdout_log: + # handler = logging.StreamHandler() + # handler.setFormatter(LOG_FORMATTER) + # log.addHandler(handler) + st = Wave2Bitstream(FILENAME, @@ -874,7 +901,7 @@ def print_bit_list_stats(bit_list): # mid_volume_ratio=0.2, hysteresis_ratio=0.1 ) bitstream = iter(st) - bitstream.sync(16) + bitstream.sync(32) bitstream = itertools.imap(lambda x: x[1], bitstream) bit_list = array.array('B', bitstream) diff --git a/PyDC/utils.py b/PyDC/utils.py old mode 100644 new mode 100755 index 56384283..ee29d50b --- a/PyDC/utils.py +++ b/PyDC/utils.py @@ -1,4 +1,5 @@ #!/usr/bin/env python2 +# encoding:utf-8 """ :copyleft: 2013 by Jens Diemer @@ -9,6 +10,16 @@ import time import collections import itertools +import logging + + +LOG_FORMATTER = logging.Formatter("") # %(asctime)s %(message)s") +LOG_LEVEL_DICT = { + 0: logging.ERROR, + 1: logging.WARNING, + 2: logging.INFO, + 3: logging.DEBUG +} def human_duration(t): @@ -79,12 +90,12 @@ def __init__(self, total, use_last_rates=4): def update(self, count): current_duration = time.time() - self.last_update - current_rate = float(count) / current_duration - self.rate_info.append(current_rate) - self.rate_info = self.rate_info[-self.use_last_rates:] - smoothed_rate = sum(self.rate_info) / len(self.rate_info) - rest = self.total - count try: + current_rate = float(count) / current_duration + self.rate_info.append(current_rate) + self.rate_info = self.rate_info[-self.use_last_rates:] + smoothed_rate = sum(self.rate_info) / len(self.rate_info) + rest = self.total - count eta = rest / smoothed_rate except ZeroDivisionError: # e.g. called before a "count+=1" @@ -266,71 +277,26 @@ def get_diff(l): def iter_pare_sum(data): - """ - >>> def g(data): - ... for i in data: yield i - >>> list(iter_pare_sum(g([5,5,10,10,4,4,10,10,5,5]))) - [20, 8, 20, 10] - - >>> list(iter_pare_sum([5,10,10,4,4,10,10,5,5,10])) - [20, 8, 20, 10] - - >>> list(iter_pare_sum([5,4,10,11,5,4,21,22,2,1])) - [21, 9, 43, 3] - - >>> list(iter_pare_sum([ - ... 5,5,10,10,5,5, - ... 5, # <- resync 1 - ... 8,8,5,5,10,10, - ... 10, # <- resync 2 - ... 7,7,5,5,10,10,2,2,3,3 - ... ])) - [20, 10, 10, 16, 10, 20, 14, 10, 20, 4, 6] - - resync ^ ^ - """ - for previous, current, next_value in itertools.islice(iter_window(data, window_size=3), 1, None, 2): - diff1 = abs(previous - current) - diff2 = abs(current - next_value) - - if diff1 < diff2: - yield previous + current - else: - yield current + next_value - - -def iter_pare_sum2(data): """ >>> def g(data): ... for no, i in enumerate(data): yield (no, i) - >>> l = [5,5,10,10,4,10,10,5,5] - >>> list(g(l)) - [(0, 5), (1, 5), (2, 10), (3, 10), (4, 4), (5, 4), (6, 10), (7, 10), (8, 5), (9, 5)] - >>> list(iter_pare_sum2(g(l))) - [(2, 20), (4, 8), (6, 20), (8, 10)] - - >>> list(iter_pare_sum2(g([5,10,10,4,4,10,10,5,5,10]))) - [(2, 20), (4, 8), (6, 20), (8, 10)] - - >>> list(iter_pare_sum2(g([5,4,10,11,5,4,21,22,2,1]))) - [(2, 21), (4, 9), (6, 43), (8, 3)] - - >>> list(iter_pare_sum2(g([ - ... 5,5,10,10,5,5, - ... 5, # <- resync 1 - ... 8,8,5,5,10,10, - ... 10, # <- resync 2 - ... 7,7,5,5,10,10,2,2,3,3 - ... ]))) - [(2, 20), (4, 10), (6, 10), (8, 16), (10, 10), (12, 20), (14, 14), (16, 10), (18, 20), (20, 4), (22, 6)] - + >>> l = [5,5,10,10,5,5,10,10,5,5,10,10,10,10,5,5,5,5] + >>> len(l) + 18 + >>> list(iter_pare_sum(g(l))) + [(2, 20), (4, 10), (6, 20), (8, 10), (10, 20), (12, 20), (14, 10), (16, 10)] - [20, 10, 10, 16, 10, 20, 14, 10, 20, 4, 6] - resync ^ ^ + >>> l = [5,10,10,5,5,10,10,5,5,10,10,10,10,5,5,5,5] + >>> len(l) + 17 + >>> list(iter_pare_sum(g(l))) + [(2, 20), (4, 10), (6, 20), (8, 10), (10, 20), (12, 20), (14, 10), (16, 10)] + [(2, 20), (4, 10), (6, 20), (8, 10), (10, 20), (12, 20), (14, 10)] """ - for previous, current, next_value in itertools.islice(iter_window(data, window_size=3), 1, None, 2): + for previous, current, next_value in itertools.islice(iter_window(data, window_size=3), 0, None, 2): + # ~ print previous, current, next_value diff1 = abs(previous[1] - current[1]) diff2 = abs(current[1] - next_value[1]) @@ -414,16 +380,16 @@ def feed(self, value): print doctest.testmod() - import math + # ~ import math - count = 32 - max_value = 255 - width = 79 + # ~ count = 32 + # ~ max_value = 255 + # ~ width = 79 - tl = TextLevelMeter(max_value, width) - for index in xrange(0, count + 1): - angle = 360.0 / count * index - y = math.sin(math.radians(angle)) * max_value - y = round(y) - print tl.feed(y) - # print "%i - %.1f° %i" % (index, angle, y) + # ~ tl = TextLevelMeter(max_value, width) + # ~ for index in xrange(0, count + 1): + # ~ angle = 360.0 / count * index + # ~ y = math.sin(math.radians(angle)) * max_value + # ~ y = round(y) + # ~ print tl.feed(y) + # ~ # print "%i - %.1f� %i" % (index, angle, y) diff --git a/PyDC/wave2bitstream.py b/PyDC/wave2bitstream.py index a6b97e84..5df79735 100644 --- a/PyDC/wave2bitstream.py +++ b/PyDC/wave2bitstream.py @@ -15,20 +15,14 @@ # own modules from utils import average, diff_info, print_bitlist, TextLevelMeter, iter_window, \ - human_duration, ProcessInfo + human_duration, ProcessInfo, LOG_LEVEL_DICT, LOG_FORMATTER import struct import time log = logging.getLogger("PyDC") -LOG_FORMATTER = logging.Formatter("") # %(asctime)s %(message)s") -LOG_LEVEL_DICT = { - 0: logging.ERROR, - 1: logging.WARNING, - 2: logging.INFO, - 3: logging.DEBUG -} + class Wave2Bitstream(object): @@ -112,7 +106,6 @@ def __init__(self, wave_filename, self.half_sinus = False # in trigger yield the full cycle - # create the generator chain: # get frame numer + volume value from the WAVE file @@ -120,30 +113,29 @@ def __init__(self, wave_filename, # triggered frame numbers of a half sinus cycle self.iter_trigger_generator = self.iter_trigger(self.wave_values_generator) -# self.iter_trigger_generator = self.iter_simple_trigger(self.wave_values_generator) # duration of a complete sinus cycle self.iter_duration_generator = self.iter_duration(self.iter_trigger_generator) - self.auto_sync_duration_generator = self.auto_sync_duration(self.iter_duration_generator) +# self.auto_sync_duration_generator = self.auto_sync_duration(self.iter_duration_generator) # build from sinus cycle duration the bit stream - self.iter_bitstream_generator = self.iter_bitstream(self.auto_sync_duration_generator) + self.iter_bitstream_generator = self.iter_bitstream(self.iter_duration_generator) def sync(self, length): """ synchronized weave sync trigger """ - return + # go in wave stream to the first bit try: first_bit_frame_no, first_bit = self.next() except StopIteration: print "Error: no bits identified!" sys.exit(-1) - print "First bit is at:", first_bit_frame_no - print "enable half sinus scan" + log.info("First bit is at: %s" % first_bit_frame_no) + log.debug("enable half sinus scan") self.half_sinus = True # Toggle sync test by consuming one half sinus sample @@ -160,17 +152,17 @@ def sync(self, length): # test_durations = itertools.imap(lambda x: x[1], test_durations) diff1, diff2 = diff_info(test_durations) - print "sync diff info: %i vs. %i" % (diff1, diff2) + log.debug("sync diff info: %i vs. %i" % (diff1, diff2)) if diff1 > diff2: - print "Sync one step." + log.info("Sync one step.") self.iter_trigger_generator.next() - print "Synced." + log.debug("Synced.") else: - print "No sync needed." + log.info("No sync needed.") self.half_sinus = False - print "disable half sinus scan" + log.debug("disable half sinus scan") def __iter__(self): return self @@ -274,28 +266,19 @@ def auto_sync_duration(self, iter_duration): diff2 = abs(current[1] - next_value[1]) if diff1 < diff2: - log.debug("EVEN") - yield (current[0], previous[1] + current[1]) + result = (previous[0], previous[1] + current[1]) + log.debug("EVEN _%s_ + _%s_ | %s -> %s" % ( + repr(previous), repr(current), repr(next_value), + repr(result) + )) else: - log.debug("ODD") - yield (current[0], current[1] + next_value[1]) - + result = (current[0], current[1] + next_value[1]) + log.debug("ODD %s | _%s_ + _%s_ -> %s" % ( + repr(previous), repr(current), repr(next_value), + repr(result) + )) -# old_frame = next(iter_duration) -# print "old frame:", old_frame -# count = 0 -# while True: -# count += 1 -# if count > 40:sys.exit() -# durations = itertools.islice(iter_duration, 3) -# print list(durations) -# diff += abs(no1 - no2) -# -# old_frame_no = next(iter_trigger) -# for frame_no in iter_trigger: -# duration = frame_no - old_frame_no -# yield (frame_no, duration) -# old_frame_no = frame_no + yield result def iter_duration(self, iter_trigger): """ @@ -319,10 +302,11 @@ def iter_trigger(self, iter_wave_values): yield frame_no last_state = True elif last_state == True and value < -self.trigger_value: - log.debug(" ---- go into netative sinus cycle ---------------") -# if self.half_sinus: - log.debug(" -**- yield half sinus -**-------------------------") - yield frame_no + if self.half_sinus: + log.debug( + " ---- go into netative -> yield half sinus ----------" + ) + yield frame_no last_state = False # # def iter_trigger(self, iter_wave_values): @@ -425,14 +409,15 @@ def iter_wave_values(self): # sys.exit() # FILENAME = "HelloWorld1 xroar.wav" # 8Bit 22050Hz - FILENAME = "HelloWorld1 origin.wav" # 109922 frames, 16Bit wave, 44100Hz -# FILENAME = "LineNumber Test 01.wav" # tokenized BASIC +# FILENAME = "HelloWorld1 origin.wav" # 109922 frames, 16Bit wave, 44100Hz + FILENAME = "LineNumber Test 01.wav" # tokenized BASIC log_level = LOG_LEVEL_DICT[3] # args.verbosity log.setLevel(log_level) logfilename = FILENAME + ".log" # args.logfile if logfilename: + print "Log into '%s'" % logfilename handler = logging.FileHandler(logfilename, # mode='a', mode='w', @@ -455,7 +440,7 @@ def iter_wave_values(self): ) bitstream = iter(st) - bitstream.sync(16) + bitstream.sync(32) bitstream = itertools.imap(lambda x: x[1], bitstream) bit_list = array.array('B', bitstream) From 812a35f251b782ea7b298be94621a9b3c4ee065f Mon Sep 17 00:00:00 2001 From: Jens Diemer Date: Sun, 25 Aug 2013 17:46:14 +0200 Subject: [PATCH 031/151] bugfix if wave is in 8-bit --- PyDC/wave2bitstream.py | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/PyDC/wave2bitstream.py b/PyDC/wave2bitstream.py index 5df79735..3ec4b603 100644 --- a/PyDC/wave2bitstream.py +++ b/PyDC/wave2bitstream.py @@ -13,6 +13,13 @@ import itertools import logging +try: + import audioop +except ImportError, err: + # e.g. PyPy, see: http://bugs.pypy.org/msg4430 + print "Can't use audioop:", err + audioop = None + # own modules from utils import average, diff_info, print_bitlist, TextLevelMeter, iter_window, \ human_duration, ProcessInfo, LOG_LEVEL_DICT, LOG_FORMATTER @@ -355,6 +362,12 @@ def iter_wave_values(self): get_wave_block_func = functools.partial(self.wavefile.readframes, self.WAVE_READ_SIZE) skipped_values = 0 for frames in iter(get_wave_block_func, ""): + + if audioop is not None and self.samplewidth == 1: + # 8 bit samples are unsigned, see: + # http://docs.python.org/2/library/audioop.html#audioop.lin2lin + frames = audioop.bias(frames, 1, 128) + for value in array.array(typecode, frames): if abs(value) < self.min_volume: @@ -408,9 +421,9 @@ def iter_wave_values(self): ) # sys.exit() -# FILENAME = "HelloWorld1 xroar.wav" # 8Bit 22050Hz + FILENAME = "HelloWorld1 xroar.wav" # 8Bit 22050Hz # FILENAME = "HelloWorld1 origin.wav" # 109922 frames, 16Bit wave, 44100Hz - FILENAME = "LineNumber Test 01.wav" # tokenized BASIC + #~ FILENAME = "LineNumber Test 01.wav" # tokenized BASIC log_level = LOG_LEVEL_DICT[3] # args.verbosity log.setLevel(log_level) @@ -426,10 +439,10 @@ def iter_wave_values(self): handler.setFormatter(LOG_FORMATTER) log.addHandler(handler) - # if args.stdout_log: - # handler = logging.StreamHandler() - # handler.setFormatter(LOG_FORMATTER) - # log.addHandler(handler) + #~ # if args.stdout_log: + #~ handler = logging.StreamHandler() + #~ handler.setFormatter(LOG_FORMATTER) + #~ log.addHandler(handler) st = Wave2Bitstream(FILENAME, bit_nul_hz=1200, # "0" is a single cycle at 1200 Hz From cb2837b4b2b3f8f880fd84c70a6809c6c5090d76 Mon Sep 17 00:00:00 2001 From: Jens Diemer Date: Sun, 25 Aug 2013 20:16:01 +0200 Subject: [PATCH 032/151] commit current broken state --- PyDC/PyDC.py | 464 ++++++++--------------------------------- PyDC/utils.py | 21 ++ PyDC/wave2bitstream.py | 45 ++-- 3 files changed, 126 insertions(+), 404 deletions(-) diff --git a/PyDC/PyDC.py b/PyDC/PyDC.py index f1806ad1..4be3da66 100755 --- a/PyDC/PyDC.py +++ b/PyDC/PyDC.py @@ -26,23 +26,17 @@ import array import functools import itertools -from wave2bitstream import Wave2Bitstream import logging -try: - import audioop -except ImportError, err: - # e.g. PyPy, see: http://bugs.pypy.org/msg4430 - print "Can't use audioop:", err - audioop = None - log = logging.getLogger("PyDC") # own modules +from wave2bitstream import Wave2Bitstream from utils import ProcessInfo, human_duration, average, print_bitlist, \ find_iter_window, list2str, count_continuous_pattern, LOG_LEVEL_DICT, \ - LOG_FORMATTER + LOG_FORMATTER, iter_steps +from bit_utils import bits2codepoint, bit_blocks2string, bit_blocks2codepoint from basic_tokens import BASIC_TOKENS, FUNCTION_TOKEN @@ -50,8 +44,16 @@ 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 ? -LEAD_IN_PATTERN = "10101010" # 0x55 -SYNC_BYTE = "00111100" # 0x3C +# Normaly the Dragon LeadIn-Byte is 0x55: "10101010" +# but in worst-case the last null can consume the first +# null of the sync byte. +# We use a reversed version of the LeadIn-Byte to avoid this. +#~ LEAD_IN_PATTERN = "01010101" +LEAD_IN_PATTERN = [0, 1, 0, 1, 0, 1, 0, 1] +#~ LEAD_IN_PATTERN = "10101010" # 0x55 + +#~ SYNC_BYTE = "00111100" # 0x3C +SYNC_BYTE = [0,0,1,1,1,1,0,0] # 0x3C # Block types: FILENAME_BLOCK = 0x00 @@ -64,289 +66,12 @@ EOF_BLOCK: "end-of-file block", } -# WAVE_RESAMPLE = 22050 # Downsample wave file to this sample rate -# WAVE_RESAMPLE = 11025 # Downsample wave file to this sample rate -# WAVE_RESAMPLE = 8000 # Downsample wave file to this sample rate - -WAVE_RESAMPLE = None # Don't change sample rate - -WAVE_READ_SIZE = 16 * 1024 # How many frames should be read from WAVE file at once? -WAV_ARRAY_TYPECODE = { - 1: "b", # 8-bit wave file - 2: "h", # 16-bit wave file - 4: "l", # 32-bit wave file TODO: Test it -} -MIN_TOGGLE_COUNT = 4 # How many samples must be in pos/neg to count a cycle? - DISPLAY_BLOCK_COUNT = 8 # How many bit block should be printet in one line? -def iter_steps(g, steps): - """ - iterate over 'g' in blocks with a length of the given 'step' count. - - >>> for v in iter_steps([1,2,3,4,5], steps=2): v - [1, 2] - [3, 4] - [5] - >>> for v in iter_steps([1,2,3,4,5,6,7,8,9], steps=3): v - [1, 2, 3] - [4, 5, 6] - [7, 8, 9] - - 12345678 12345678 - 12345678 - >>> bits = [int(i) for i in "0101010101010101111000"] - >>> for v in iter_steps(bits, steps=8): v - [0, 1, 0, 1, 0, 1, 0, 1] - [0, 1, 0, 1, 0, 1, 0, 1] - [1, 1, 1, 0, 0, 0] - """ - values = [] - for value in g: - values.append(value) - if len(values) == steps: - yield list(values) - values = [] - if values: - yield list(values) - - -def iter_window(g, window_size): - """ - interate over 'g' bit-by-bit and yield a window with the given 'window_size' width. - - >>> for v in iter_window([1,2,3,4], window_size=2): v - [1, 2] - [2, 3] - [3, 4] - >>> for v in iter_window([1,2,3,4,5], window_size=3): v - [1, 2, 3] - [2, 3, 4] - [3, 4, 5] - - >>> for v in iter_window([1,2,3,4], window_size=2): - ... v - ... v.append(True) - [1, 2] - [2, 3] - [3, 4] - """ - values = collections.deque(maxlen=window_size) - for value in g: - values.append(value) - if len(values) == window_size: - yield list(values) - - -def iter_wave_values(wavefile): - """ - generator that yield integers for WAVE files. - - returned sample values are in this ranges: - 8-bit: -255..255 - 16-bit: -32768..32768 - 32-bit: -2147483648..2147483647 - """ - 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: - typecode = WAV_ARRAY_TYPECODE[samplewidth] - except KeyError: - raise NotImplementedError( - "Only %s wave files are supported, yet!" % ", ".join(["%sBit" % (i * 8) for i in WAV_ARRAY_TYPECODE.keys()]) - ) - - def _print_status(frame_no, framerate): - ms = float(frame_no) / framerate - rest, eta, rate = process_info.update(frame_no) - sys.stdout.write( - "\r%i frames (wav pos:%s) eta: %s (rate: %iFrames/sec) " % ( - frame_no, human_duration(ms), eta, rate - ) - ) - - framerate = wavefile.getframerate() # frames / second - frame_count = wavefile.getnframes() - - process_info = ProcessInfo(frame_count, use_last_rates=4) - start_time = time.time() - next_status = start_time + 0.25 - - new_rate = None - if audioop is not None and WAVE_RESAMPLE is not None and framerate > WAVE_RESAMPLE: - new_rate = WAVE_RESAMPLE - print "resample from %iHz/sec. to %sHz/sec" % (framerate, new_rate) - framerate = WAVE_RESAMPLE - - frame_no = 0 - ratecv_state = None - - get_wave_block_func = functools.partial(wavefile.readframes, WAVE_READ_SIZE) - for frames in iter(get_wave_block_func, ""): - - if new_rate is not None: - # downsample the wave file - # FIXME! See: http://www.python-forum.de/viewtopic.php?f=11&t=6118&p=244377#p244377 - print "before:", len(frames), new_rate - frames, ratecv_state = audioop.ratecv( - frames, samplewidth, nchannels, framerate, new_rate, ratecv_state, 1, 1 - ) - print "after:", len(frames), new_rate - - if time.time() > next_status: - next_status = time.time() + 1 - _print_status(frame_no, framerate) - - for value in array.array(typecode, frames): - frame_no += 1 - yield frame_no, value - - _print_status(frame_no, framerate) - print - MIN_SAMPLE_VALUE = 5 -def count_sign(values, min_value): - """ - >>> count_sign([3,-1,-2], 0) - (1, 2) - >>> count_sign([3,-1,-2], 2) - (1, 0) - >>> count_sign([0,-1],0) - (0, 1) - """ - positive_count = 0 - negative_count = 0 - for value in values: - if value > min_value: - positive_count += 1 - elif value < -min_value: - negative_count += 1 - return positive_count, negative_count - - -def samples2bits(samples, framerate, frame_count, even_odd): - in_positive = even_odd - in_negative = not even_odd - toggle_count = 0 # Counter for detect a complete cycle - previous_frame_no = 0 - bit_count = 0 - - process_info = ProcessInfo(frame_count, use_last_rates=4) - start_time = time.time() - next_status = start_time + 0.25 - - def _print_status(frame_no, framerate): - ms = float(frame_no) / framerate - rest, eta, rate = process_info.update(frame_no) - sys.stdout.write( - "\r%i frames (wav pos:%s) eta: %s (rate: %iFrames/sec) " % ( - frame_no, human_duration(ms), eta, rate - ) - ) - - window_values = collections.deque(maxlen=MIN_TOGGLE_COUNT) - - # Fill window deque - for frame_no, value in samples[:MIN_TOGGLE_COUNT]: - window_values.append(value) - - bit_one_min_hz = BIT_ONE_HZ - MAX_HZ_VARIATION - bit_one_max_hz = BIT_ONE_HZ + MAX_HZ_VARIATION - - bit_nul_min_hz = BIT_NUL_HZ - MAX_HZ_VARIATION - bit_nul_max_hz = BIT_NUL_HZ + MAX_HZ_VARIATION - - one_hz_count = 0 - one_hz_min = sys.maxint - one_hz_avg = None - one_hz_max = 0 - nul_hz_count = 0 - nul_hz_min = sys.maxint - nul_hz_avg = None - nul_hz_max = 0 - - old_status = (-1, -1) - for frame_no, value in samples[MIN_TOGGLE_COUNT:]: - window_values.append(value) - - new_status = count_sign(window_values, MIN_SAMPLE_VALUE) - if new_status == old_status: - # ignore the frame if status not changed -# print new_status, "skip" - continue - positive_count, negative_count = old_status = new_status - - # print window_values, positive_count, negative_count - if not in_positive and positive_count == MIN_TOGGLE_COUNT and negative_count == 0: - # go into a positive sinus area - in_positive = True - in_negative = False - toggle_count += 1 - elif not in_negative and negative_count == MIN_TOGGLE_COUNT and positive_count == 0: - # go into a negative sinus area - in_negative = True - in_positive = False - toggle_count += 1 - else: -# print "wrong:", positive_count, negative_count - continue - - if toggle_count >= 2: - # a single sinus cycle complete - toggle_count = 0 - - frame_count = frame_no - previous_frame_no - previous_frame_no = frame_no - hz = framerate / frame_count -# print "%sHz" % hz - - if hz > bit_one_min_hz and hz < bit_one_max_hz: -# print "bit 1" - bit_count += 1 - yield 1 - one_hz_count += 1 - if hz < one_hz_min: - one_hz_min = hz - if hz > one_hz_max: - one_hz_max = hz - one_hz_avg = average(one_hz_avg, hz, one_hz_count) - elif hz > bit_nul_min_hz and hz < bit_nul_max_hz: -# print "bit 0" - bit_count += 1 - yield 0 - nul_hz_count += 1 - if hz < nul_hz_min: - nul_hz_min = hz - if hz > nul_hz_max: - nul_hz_max = hz - nul_hz_avg = average(nul_hz_avg, hz, nul_hz_count) - else: - print "Skip signal with %sHz." % hz - continue - - if time.time() > next_status: - next_status = time.time() + 1 - _print_status(frame_no, framerate) - - _print_status(frame_no, framerate) - print - duration = time.time() - start_time - rate = bit_count / duration / 8 / 1024 - print "%i bits decoded from %i audio samples in %s (%.1fKBytes/s)" % ( - bit_count, frame_no, human_duration(duration), rate - ) - print - print "Bit 1: %s-%sHz avg: %.1fHz variation: %sHz" % ( - one_hz_min, one_hz_max, one_hz_avg, one_hz_max - one_hz_min - ) - print "Bit 0: %s-%sHz avg: %.1fHz variation: %sHz" % ( - nul_hz_min, nul_hz_max, nul_hz_avg, nul_hz_max - nul_hz_min - ) def pop_bytes_from_bit_list(bit_list, count): @@ -376,61 +101,11 @@ def pop_bytes_from_bit_list(bit_list, count): return bit_list, data -def bits2byte_no(bits): - """ - >>> c = bits2byte_no([0, 0, 0, 1, 0, 0, 1, 0]) - >>> c - 72 - >>> chr(c) - 'H' - - >>> bits2byte_no([0, 0, 1, 1, 0, 0, 1, 0]) - 76 - """ - bits = bits[::-1] - bits = list2str(bits) - return int(bits, 2) - -def bit_blocks2byte_no(block_bit_list): - """ - >>> bit_list = ( - ... [0,0,1,1,0,0,1,0], # L - ... [1,0,0,1,0,0,1,0], # I - ... ) - >>> bit_blocks2byte_no(bit_list) - [76, 73] - """ - byte_no = [bits2byte_no(block) for block in block_bit_list] - return byte_no - -def bit_blocks2string(block_bit_list): - """ - >>> bit_list = ( - ... [0,0,1,1,0,0,1,0], # L - ... [1,0,0,1,0,0,1,0], # I - ... ) - >>> bit_blocks2string(bit_list) - 'LI' - """ - bytes = "".join([chr(byte_no) for byte_no in bit_blocks2byte_no(block_bit_list)]) - return bytes -def byte_list2bit_list(data): - """ - >>> data = (0x0,0x1e,0x8b,0x20,0x49,0x0) - >>> byte_list2bit_list(data) - ['00000000', '01111000', '11010001', '00000100', '10010010', '00000000'] - """ - bit_list = [] - for char in data: - bits = '{0:08b}'.format(char) - bits = bits[::-1] - bit_list.append(bits) - return bit_list def print_block_table(block_bit_list): for block in block_bit_list: - byte_no = bits2byte_no(block) + byte_no = bits2codepoint(block) character = chr(byte_no) print "%s %4s %3s %s" % ( list2str(block), hex(byte_no), byte_no, repr(character) @@ -440,7 +115,7 @@ def print_block_table(block_bit_list): def print_as_hex(block_bit_list): line = "" for block in block_bit_list: - byte_no = bits2byte_no(block) + byte_no = bits2codepoint(block) character = chr(byte_no) line += hex(byte_no) print line @@ -449,7 +124,7 @@ def print_as_hex(block_bit_list): def print_as_hex_list(block_bit_list): line = [] for block in block_bit_list: - byte_no = bits2byte_no(block) + byte_no = bits2codepoint(block) character = chr(byte_no) line.append(hex(byte_no)) print ",".join(line) @@ -458,31 +133,59 @@ def print_as_hex_list(block_bit_list): def get_block_info(bit_list): # Searching for lead-in byte leader_pos = find_iter_window(bit_list, LEAD_IN_PATTERN) # Search for LEAD_IN_PATTERN in bit-by-bit steps - print "Start leader '%s' found at position: %i" % (LEAD_IN_PATTERN, leader_pos) + print "Start leader '%s' found at position: %i" % ( + list2str(LEAD_IN_PATTERN), leader_pos + ) # Cut bits before the first 01010101 start leader - print "bits before header:", repr(list2str(bit_list[:leader_pos])) - bit_list = bit_list[leader_pos:] + bits_before_header = itertools.islice(bit_list, leader_pos) + print "bits before header:", repr(list2str(bits_before_header)) + #~ bit_list = bit_list[leader_pos:] + + # consume lead-in byte matches without ceasing to get faster to the sync-byte + #~ def not_LeaderByte(x): + #~ return x == LEAD_IN_PATTERN + + #~ leader_count=0 + #~ for leader_count, _ in enumerate(itertools.takewhile(not_LeaderByte, iter_steps(bit_list, len(LEAD_IN_PATTERN)))): + #~ continue + #~ print + #~ print "Found %i leader bytes" % leader_count + #~ if leader_count == 0: + #~ print "WARNING: leader bytes not found! Maybe 'even_odd' bool wrong???" - # count lead-in byte matches without ceasing to get faster to the sync-byte - leader_count = count_continuous_pattern(bit_list, LEAD_IN_PATTERN) - print "Found %i leader bytes" % leader_count - if leader_count == 0: - print "WARNING: leader bytes not found! Maybe 'even_odd' bool wrong???" - to_cut = leader_count * 8 - bit_list = bit_list[to_cut:] + print "-"*79 + print_bitlist(bit_list) + print "-"*79 # Search for SYNC_BYTE in bit-by-bit steps # to get a byte-synchronized bit-sequence sync_pos = find_iter_window(bit_list, SYNC_BYTE) + print print "Find sync byte after %i Bits" % sync_pos - to_cut = sync_pos + 8 # Bits before sync byte + sync byte - bit_list = bit_list[to_cut:] + #~ to_cut = sync_pos + 8 # Bits before sync byte + sync byte + #~ bit_list = bit_list[to_cut:] - bit_list, bytes = pop_bytes_from_bit_list(bit_list, count=2) + #~ bit_list, bytes = pop_bytes_from_bit_list(bit_list, count=2) - block_type = bits2byte_no(bytes[0]) - block_length = bits2byte_no(bytes[1]) + print "-"*79 + print_bitlist(bit_list) + print "-"*79 + + #~ block_type = itertools.islice(bit_list, 16) + #~ block_length = itertools.islice(bit_list, 16) + block_type = get_word(bit_list) + print "raw block type:", repr(block_type) + block_length = get_word(bit_list) + print "raw block length:", repr(block_length) + + #~ block_type = bits2codepoint(bytes[0]) + #~ block_length = bits2codepoint(bytes[1]) + + #~ print "-"*79 + #~ print_bitlist(bit_list) + #~ print "-"*79 + #~ sys.exit() return bit_list, block_type, block_length @@ -729,7 +432,7 @@ def __init__(self, file_block_data): self.filename = bit_blocks2string(block_bit_list[:8]) - byte_no_block = bit_blocks2byte_no(file_block_data[8:]) + byte_no_block = bit_blocks2codepoint(file_block_data[8:]) print "file meta:", repr(byte_no_block) self.file_type = byte_no_block[0] @@ -762,7 +465,7 @@ def __init__(self, file_block_data): def add_block_data(self, block_length, block_bit_list): print "raw data length: %iBytes" % len(block_bit_list) # print_as_hex_list(block_bit_list) - data = iter([bits2byte_no(bit_block) for bit_block in block_bit_list]) + data = iter([bits2codepoint(bit_block) for bit_block in block_bit_list]) if self.is_tokenized: self.file_content.add_block_data(block_length, data) else: @@ -824,7 +527,7 @@ def print_bit_list_stats(bit_list): # created by Xroar Emulator -# FILENAME = "HelloWorld1 xroar.wav" # 8Bit 22050Hz + FILENAME = "HelloWorld1 xroar.wav" # 8Bit 22050Hz # Bit 1 min: 1696Hz avg: 2058.3Hz max: 2205Hz variation: 509Hz # Bit 0 min: 595Hz avg: 1090.4Hz max: 1160Hz Variation: 565Hz # 4760 Bits: 2243 positive bits and 2517 negative bits @@ -833,7 +536,7 @@ def print_bit_list_stats(bit_list): # created by origin Dragon 32 machine # 16Bit 44.1KHz mono -# FILENAME = "HelloWorld1 origin.wav" # no sync neede + #~ FILENAME = "HelloWorld1 origin.wav" # no sync neede # Bit 1 min: 1764Hz avg: 2013.9Hz max: 2100Hz variation: 336Hz # Bit 0 min: 595Hz avg: 1090.2Hz max: 1336Hz Variation: 741Hz # 2710 Bits: 1217 positive bits and 1493 negative bits @@ -858,7 +561,7 @@ def print_bit_list_stats(bit_list): # FILENAME = "Quickbeam Software - Duplicas v3.0.wav" # binary! - FILENAME = "Dragon Data Ltd - Examples from the Manual - 39~58 [run].wav" + #~ FILENAME = "Dragon Data Ltd - Examples from the Manual - 39~58 [run].wav" # Bit 1 min: 1696Hz avg: 2004.0Hz max: 2004Hz variation: 308Hz # Bit 0 min: 1025Hz avg: 1025.0Hz max: 1025Hz Variation: 0Hz # 155839 Bits: 73776 positive bits and 82063 negative bits @@ -872,19 +575,19 @@ def print_bit_list_stats(bit_list): - log_level = LOG_LEVEL_DICT[3] # args.verbosity - log.setLevel(log_level) + #~ log_level = LOG_LEVEL_DICT[3] # args.verbosity + #~ log.setLevel(log_level) - logfilename = FILENAME + ".log" # args.logfile - if logfilename: - print "Log into '%s'" % logfilename - handler = logging.FileHandler(logfilename, - # mode='a', - mode='w', - encoding="utf8" - ) - handler.setFormatter(LOG_FORMATTER) - log.addHandler(handler) + #~ logfilename = FILENAME + ".log" # args.logfile + #~ if logfilename: + #~ print "Log into '%s'" % logfilename + #~ handler = logging.FileHandler(logfilename, + #~ # mode='a', + #~ mode='w', + #~ encoding="utf8" + #~ ) + #~ handler.setFormatter(LOG_FORMATTER) + #~ log.addHandler(handler) # if args.stdout_log: # handler = logging.StreamHandler() @@ -897,17 +600,16 @@ def print_bit_list_stats(bit_list): bit_nul_hz=1200, # "0" is a single cycle at 1200 Hz bit_one_hz=2400, # "1" is a single cycle at 2400 Hz hz_variation=450, # How much Hz can signal scatter to match 1 or 0 bit ? -# min_volume_ratio=0.01, # Ignore sample frames if lower volume -# mid_volume_ratio=0.2, hysteresis_ratio=0.1 ) bitstream = iter(st) bitstream.sync(32) - bitstream = itertools.imap(lambda x: x[1], bitstream) - bit_list = array.array('B', bitstream) + bitstream = itertools.imap(lambda x: x[1], bitstream) # remove frame_no - print "-"*79 - print_bitlist(bit_list) - print "-"*79 + #~ bit_list = array.array('B', bitstream) + + #~ print "-"*79 + #~ print_bitlist(bit_list) + #~ print "-"*79 # print_block_table(bit_list) # print "-"*79 # sys.exit() @@ -916,7 +618,7 @@ def print_bit_list_stats(bit_list): while True: print "_"*79 - bit_list, block_type, block_length = get_block_info(bit_list) + bit_list, block_type, block_length = get_block_info(bitstream) try: block_type_name = BLOCK_TYPE_DICT[block_type] except KeyError: diff --git a/PyDC/utils.py b/PyDC/utils.py index ee29d50b..cb5fbf84 100755 --- a/PyDC/utils.py +++ b/PyDC/utils.py @@ -177,6 +177,7 @@ def iter_window(g, window_size): if len(values) == window_size: yield list(values) + def count_continuous_pattern(bits, pattern): """ count 'pattern' matches without ceasing. @@ -202,6 +203,7 @@ def count_continuous_pattern(bits, pattern): """ pattern_len = len(pattern) pattern = [int(i) for i in pattern] + count=-1 for count, data in enumerate(iter_steps(bits, pattern_len), 1): if data != pattern: count -= 1 @@ -375,6 +377,25 @@ def feed(self, value): return self.source_msg[:value] + "*" + self.source_msg[value + 1:] +def count_sign(values, min_value): + """ + >>> count_sign([3,-1,-2], 0) + (1, 2) + >>> count_sign([3,-1,-2], 2) + (1, 0) + >>> count_sign([0,-1],0) + (0, 1) + """ + positive_count = 0 + negative_count = 0 + for value in values: + if value > min_value: + positive_count += 1 + elif value < -min_value: + negative_count += 1 + return positive_count, negative_count + + if __name__ == "__main__": import doctest print doctest.testmod() diff --git a/PyDC/wave2bitstream.py b/PyDC/wave2bitstream.py index 3ec4b603..cd0f4d3b 100644 --- a/PyDC/wave2bitstream.py +++ b/PyDC/wave2bitstream.py @@ -52,9 +52,8 @@ def __init__(self, wave_filename, bit_nul_hz, # sinus cycle frequency in Hz for one "0" bit bit_one_hz, # sinus cycle frequency in Hz for one "1" bit hz_variation, # How much Hz can signal scatter to match 1 or 0 bit ? - min_volume_ratio=1, # Ignore sample frames if lower volume - mid_volume_ratio=5, - hysteresis_ratio=0.1 + min_volume_ratio=5, # Ignore sample frames if lower volume + mid_volume_ratio=10, ): self.wave_filename = wave_filename @@ -85,18 +84,6 @@ def __init__(self, wave_filename, self.trigger_value = int(round(self.max_value * mid_volume_ratio / 100)) print "Use trigger value: %.f%% = %i" % (mid_volume_ratio, self.trigger_value) -# hysteresis_shift = self.trigger_value * hysteresis_ratio -# print "Hysteresis shift:", hysteresis_shift -# -# self.hysteresis_min = self.trigger_value - hysteresis_shift -# assert self.hysteresis_min > 0, "hysteresis ratio to big! (min: %s)" % self.hysteresis_min -# -# self.hysteresis_max = self.trigger_value + hysteresis_shift -# assert self.hysteresis_max < self.max_value, "hysteresis ratio to big! (max: %s)" % self.hysteresis_max -# -# print "hysteresis trigger valume: %i - %i" % (self.hysteresis_min, self.hysteresis_max) - - # build min/max Hz values self.bit_nul_min_hz = bit_nul_hz - hz_variation self.bit_nul_max_hz = bit_nul_hz + hz_variation @@ -305,13 +292,19 @@ def iter_trigger(self, iter_wave_values): last_state = True for frame_no, value in iter_wave_values: if last_state == False and value > self.trigger_value: - log.debug(" ==== go into positive sinus cycle ===============") + log.debug( + " ==== go into positive sinus cycle (%s > %s)===============" % ( + value, self.trigger_value + ) + ) yield frame_no last_state = True elif last_state == True and value < -self.trigger_value: if self.half_sinus: log.debug( - " ---- go into netative -> yield half sinus ----------" + " ---- go into netative -> yield half sinus (%s < -%s) ----------" % ( + value, self.trigger_value + ) ) yield frame_no last_state = False @@ -379,11 +372,17 @@ def iter_wave_values(self): skipped_values = 0 msg = tlm.feed(value) - log.debug( - "%s value: %i (%.1f%%)" % (msg, value, abs(self.max_value / value)) - ) + if log.level>=logging.DEBUG: + #~ try: + percent = 100.0 / self.max_value * abs(value) + #~ except + + log.debug( + "%s value: %i (%.1f%%)" % (msg, value, percent) + ) frame_no += 1 + #~ if frame_no>100:sys.exit() yield frame_no, value # def iter_wave_valuesOLD(self): @@ -421,8 +420,8 @@ def iter_wave_values(self): ) # sys.exit() - FILENAME = "HelloWorld1 xroar.wav" # 8Bit 22050Hz -# FILENAME = "HelloWorld1 origin.wav" # 109922 frames, 16Bit wave, 44100Hz + #~ FILENAME = "HelloWorld1 xroar.wav" # 8Bit 22050Hz + FILENAME = "HelloWorld1 origin.wav" # 109922 frames, 16Bit wave, 44100Hz #~ FILENAME = "LineNumber Test 01.wav" # tokenized BASIC log_level = LOG_LEVEL_DICT[3] # args.verbosity @@ -439,7 +438,7 @@ def iter_wave_values(self): handler.setFormatter(LOG_FORMATTER) log.addHandler(handler) - #~ # if args.stdout_log: + # if args.stdout_log: #~ handler = logging.StreamHandler() #~ handler.setFormatter(LOG_FORMATTER) #~ log.addHandler(handler) From d4e86fa96a6b3425ca3a35e4f006c9231a214df1 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Mon, 26 Aug 2013 14:20:46 +0200 Subject: [PATCH 033/151] commit broken state --- PyDC/PyDC.py | 274 ++++++++++++++++------------- PyDC/utils.py | 383 +++++++++++++++++++++++++++++++++-------- PyDC/wave2bitstream.py | 73 ++++---- 3 files changed, 506 insertions(+), 224 deletions(-) diff --git a/PyDC/PyDC.py b/PyDC/PyDC.py index 4be3da66..0bdb3096 100755 --- a/PyDC/PyDC.py +++ b/PyDC/PyDC.py @@ -19,12 +19,7 @@ """ -import collections -import wave import sys -import time -import array -import functools import itertools import logging @@ -32,12 +27,11 @@ # own modules -from wave2bitstream import Wave2Bitstream -from utils import ProcessInfo, human_duration, average, print_bitlist, \ - find_iter_window, list2str, count_continuous_pattern, LOG_LEVEL_DICT, \ - LOG_FORMATTER, iter_steps -from bit_utils import bits2codepoint, bit_blocks2string, bit_blocks2codepoint from basic_tokens import BASIC_TOKENS, FUNCTION_TOKEN +from utils import find_iter_window, iter_steps, MaxPosArraived, \ + print_bitlist, bits2codepoint, list2str, bitstream2codepoints, get_word, \ + print_codepoint_stream +from wave2bitstream import Wave2Bitstream BIT_ONE_HZ = 2400 # "1" is a single cycle at 2400 Hz @@ -48,12 +42,12 @@ # but in worst-case the last null can consume the first # null of the sync byte. # We use a reversed version of the LeadIn-Byte to avoid this. -#~ LEAD_IN_PATTERN = "01010101" +# LEAD_IN_PATTERN = "01010101" LEAD_IN_PATTERN = [0, 1, 0, 1, 0, 1, 0, 1] -#~ LEAD_IN_PATTERN = "10101010" # 0x55 +# LEAD_IN_PATTERN = "10101010" # 0x55 -#~ SYNC_BYTE = "00111100" # 0x3C -SYNC_BYTE = [0,0,1,1,1,1,0,0] # 0x3C +# SYNC_BYTE = "00111100" # 0x3C +SYNC_BYTE = [0, 0, 1, 1, 1, 1, 0, 0] # 0x3C # Block types: FILENAME_BLOCK = 0x00 @@ -103,8 +97,8 @@ def pop_bytes_from_bit_list(bit_list, count): -def print_block_table(block_bit_list): - for block in block_bit_list: +def print_block_table(block_codepoints): + for block in block_codepoints: byte_no = bits2codepoint(block) character = chr(byte_no) print "%s %4s %3s %s" % ( @@ -112,96 +106,126 @@ def print_block_table(block_bit_list): ) -def print_as_hex(block_bit_list): +def print_as_hex(block_codepoints): line = "" - for block in block_bit_list: + for block in block_codepoints: byte_no = bits2codepoint(block) character = chr(byte_no) line += hex(byte_no) print line -def print_as_hex_list(block_bit_list): +def print_as_hex_list(block_codepoints): line = [] - for block in block_bit_list: + for block in block_codepoints: byte_no = bits2codepoint(block) character = chr(byte_no) line.append(hex(byte_no)) print ",".join(line) -def get_block_info(bit_list): - # Searching for lead-in byte - leader_pos = find_iter_window(bit_list, LEAD_IN_PATTERN) # Search for LEAD_IN_PATTERN in bit-by-bit steps - print "Start leader '%s' found at position: %i" % ( - list2str(LEAD_IN_PATTERN), leader_pos - ) - # Cut bits before the first 01010101 start leader - bits_before_header = itertools.islice(bit_list, leader_pos) - print "bits before header:", repr(list2str(bits_before_header)) - #~ bit_list = bit_list[leader_pos:] - - # consume lead-in byte matches without ceasing to get faster to the sync-byte - #~ def not_LeaderByte(x): - #~ return x == LEAD_IN_PATTERN - - #~ leader_count=0 - #~ for leader_count, _ in enumerate(itertools.takewhile(not_LeaderByte, iter_steps(bit_list, len(LEAD_IN_PATTERN)))): - #~ continue - #~ print - #~ print "Found %i leader bytes" % leader_count - #~ if leader_count == 0: - #~ print "WARNING: leader bytes not found! Maybe 'even_odd' bool wrong???" - - print "-"*79 - print_bitlist(bit_list) - print "-"*79 - - # Search for SYNC_BYTE in bit-by-bit steps - # to get a byte-synchronized bit-sequence - sync_pos = find_iter_window(bit_list, SYNC_BYTE) - print - print "Find sync byte after %i Bits" % sync_pos - #~ to_cut = sync_pos + 8 # Bits before sync byte + sync byte - #~ bit_list = bit_list[to_cut:] - - #~ bit_list, bytes = pop_bytes_from_bit_list(bit_list, count=2) - - print "-"*79 - print_bitlist(bit_list) - print "-"*79 - - #~ block_type = itertools.islice(bit_list, 16) - #~ block_length = itertools.islice(bit_list, 16) - block_type = get_word(bit_list) - print "raw block type:", repr(block_type) - block_length = get_word(bit_list) - print "raw block length:", repr(block_length) - #~ block_type = bits2codepoint(bytes[0]) - #~ block_length = bits2codepoint(bytes[1]) +LEADER_MAX_POS = 8 * 256 # Max search window for get the Leader-Byte? + +def get_block_info(bitstream): - #~ print "-"*79 - #~ print_bitlist(bit_list) - #~ print "-"*79 - #~ sys.exit() +# bitstream = iter(itertools.islice(bitstream, 2500)) # TEST +# for i in xrange(119): +# next(bitstream) +# print "-"*79 +# # print_bitlist(bitstream, no_repr=True) +# print_bitlist(bitstream) +# print "-"*79 +# sys.exit() - return bit_list, block_type, block_length + """ + 00111100 + 00000000 + 11110000 + 00000100 + 00000100 + + + 240 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 + 248 - 00111100 00000000 11110000 00000100 00000100 00000100 00000100 00000100 + 256 - 00000100 00000100 00000100 00000000 00000000 00000000 00000000 00000000 + 264 - 00000000 00000000 11110000 10101010 10101010 10101010 10101010 10101010 + + 240 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 + 0x55 'U' 0x55 'U' 0x55 'U' 0x55 'U' 0x55 'U' 0x55 'U' 0x55 'U' 0x55 'U' + 248 - 00111100 00000000 11110000 00000100 00000100 00000100 00000100 00000100 + 0x3c '<' 0x0 0xf 0x20 ' ' 0x20 ' ' 0x20 ' ' 0x20 ' ' 0x20 ' ' + 256 - 00000100 00000100 00000100 00000000 00000000 00000000 00000000 00000000 + 0x20 ' ' 0x20 ' ' 0x20 ' ' 0x0 0x0 0x0 0x0 0x0 + 264 - 00000000 00000000 11110000 10101010 10101010 10101010 10101010 10101010 + 0x0 0x0 0xf 0x55 'U' 0x55 'U' 0x55 'U' 0x55 'U' 0x55 'U' + """ + # Searching for lead-in byte +# try: +# +# leader_pos = find_iter_window(bitstream, LEAD_IN_PATTERN, LEADER_MAX_POS) +# except MaxPosArraived, err: +# print "\nError: Leader-Byte not found in the first %i Bytes! (%s)" % ( +# LEADER_MAX_POS, err +# ) +# sys.exit(-1) +# else: +# print "\nLeader-Byte '%s' found at %i Bytes" % (list2str(LEAD_IN_PATTERN), leader_pos) + +# print "-"*79 +# print_bitlist(bitstream, no_repr=True) +# print "-"*79 +# sys.exit() + + try: + sync_pos = find_iter_window(bitstream, SYNC_BYTE, LEADER_MAX_POS) + except MaxPosArraived, err: + print "\nError: Sync-Byte not found in the first %i Bytes! (%s)" % ( + LEADER_MAX_POS, err + ) + sys.exit(-1) + else: + print "\nSync-Byte '%s' found at %i Bytes" % (list2str(SYNC_BYTE), sync_pos) + +# print "-"*79 +# print_bitlist(bitstream, no_repr=True) +# print "-"*79 +# sys.exit() -def get_word(byte_iterator): """ - return a uint16 value - - >>> g=iter([0x1e, 0x12]) - >>> v=get_word(g) - >>> v - 7698 - >>> hex(v) - '0x1e12' + 00111100 00000000 111100000 00001000 00001000 00001000 00001000 00001000 00001000 00001000 + + 00111100 + 00000000 + 11110000 + 00000100 + 00000100 + + 0 00001000 00001000 00001000 00001000 00001000 """ - return (next(byte_iterator) << 8) | next(byte_iterator) + +# print "-"*79 +# print_bitlist(bitstream) +# print "-"*79 +# sys.exit() + + codepoint_stream = bitstream2codepoints(bitstream) +# print "-"*79 +# print_codepoint_stream(codepoint_stream) +# print "-"*79 + + block_type = get_word(codepoint_stream) + print "raw block type:", repr(block_type), hex(block_type) + block_length = get_word(codepoint_stream) + print "raw block length:", repr(block_length) + +# sys.exit() + + return block_type, block_length + + def bytes2codeline(raw_bytes): @@ -426,11 +450,11 @@ class CassetteFile(object): of a binary file. """ def __init__(self, file_block_data): -# print_block_bit_list(block_bit_list) - print_block_table(block_bit_list) +# print_block_codepoints(block_codepoints) + print_block_table(block_codepoints) print_as_hex_list(file_block_data) - self.filename = bit_blocks2string(block_bit_list[:8]) + self.filename = bit_blocks2string(block_codepoints[:8]) byte_no_block = bit_blocks2codepoint(file_block_data[8:]) print "file meta:", repr(byte_no_block) @@ -462,10 +486,10 @@ def __init__(self, file_block_data): self.file_content = FileContent() - def add_block_data(self, block_length, block_bit_list): - print "raw data length: %iBytes" % len(block_bit_list) -# print_as_hex_list(block_bit_list) - data = iter([bits2codepoint(bit_block) for bit_block in block_bit_list]) + def add_block_data(self, block_length, block_codepoints): + print "raw data length: %iBytes" % len(block_codepoints) +# print_as_hex_list(block_codepoints) + data = iter([bits2codepoint(bit_block) for bit_block in block_codepoints]) if self.is_tokenized: self.file_content.add_block_data(block_length, data) else: @@ -483,15 +507,15 @@ def __init__(self): self.files = [] self.current_file = None - def add_block(self, block_type, block_length, block_bit_list): + def add_block(self, block_type, block_length, block_codepoints): if block_type == EOF_BLOCK: return elif block_type == FILENAME_BLOCK: - self.current_file = CassetteFile(block_bit_list) + self.current_file = CassetteFile(block_codepoints) print "Add file %s" % repr(self.current_file) self.files.append(self.current_file) elif block_type == DATA_BLOCK: - self.current_file.add_block_data(block_length, block_bit_list) + self.current_file.add_block_data(block_length, block_codepoints) else: raise TypeError("Block type %s unkown!" & hex(block_type)) @@ -527,7 +551,7 @@ def print_bit_list_stats(bit_list): # created by Xroar Emulator - FILENAME = "HelloWorld1 xroar.wav" # 8Bit 22050Hz +# FILENAME = "HelloWorld1 xroar.wav" # 8Bit 22050Hz # Bit 1 min: 1696Hz avg: 2058.3Hz max: 2205Hz variation: 509Hz # Bit 0 min: 595Hz avg: 1090.4Hz max: 1160Hz Variation: 565Hz # 4760 Bits: 2243 positive bits and 2517 negative bits @@ -536,7 +560,7 @@ def print_bit_list_stats(bit_list): # created by origin Dragon 32 machine # 16Bit 44.1KHz mono - #~ FILENAME = "HelloWorld1 origin.wav" # no sync neede + FILENAME = "HelloWorld1 origin.wav" # no sync neede # Bit 1 min: 1764Hz avg: 2013.9Hz max: 2100Hz variation: 336Hz # Bit 0 min: 595Hz avg: 1090.2Hz max: 1336Hz Variation: 741Hz # 2710 Bits: 1217 positive bits and 1493 negative bits @@ -561,7 +585,7 @@ def print_bit_list_stats(bit_list): # FILENAME = "Quickbeam Software - Duplicas v3.0.wav" # binary! - #~ FILENAME = "Dragon Data Ltd - Examples from the Manual - 39~58 [run].wav" + # FILENAME = "Dragon Data Ltd - Examples from the Manual - 39~58 [run].wav" # Bit 1 min: 1696Hz avg: 2004.0Hz max: 2004Hz variation: 308Hz # Bit 0 min: 1025Hz avg: 1025.0Hz max: 1025Hz Variation: 0Hz # 155839 Bits: 73776 positive bits and 82063 negative bits @@ -575,19 +599,19 @@ def print_bit_list_stats(bit_list): - #~ log_level = LOG_LEVEL_DICT[3] # args.verbosity - #~ log.setLevel(log_level) + # log_level = LOG_LEVEL_DICT[3] # args.verbosity + # log.setLevel(log_level) - #~ logfilename = FILENAME + ".log" # args.logfile - #~ if logfilename: - #~ print "Log into '%s'" % logfilename - #~ handler = logging.FileHandler(logfilename, - #~ # mode='a', - #~ mode='w', - #~ encoding="utf8" - #~ ) - #~ handler.setFormatter(LOG_FORMATTER) - #~ log.addHandler(handler) + # logfilename = FILENAME + ".log" # args.logfile + # if logfilename: + # print "Log into '%s'" % logfilename + # handler = logging.FileHandler(logfilename, + # # mode='a', + # mode='w', + # encoding="utf8" + # ) + # handler.setFormatter(LOG_FORMATTER) + # log.addHandler(handler) # if args.stdout_log: # handler = logging.StreamHandler() @@ -605,11 +629,11 @@ def print_bit_list_stats(bit_list): bitstream.sync(32) bitstream = itertools.imap(lambda x: x[1], bitstream) # remove frame_no - #~ bit_list = array.array('B', bitstream) + # bit_list = array.array('B', bitstream) - #~ print "-"*79 - #~ print_bitlist(bit_list) - #~ print "-"*79 + # print "-"*79 + # print_bitlist(bit_list) + # print "-"*79 # print_block_table(bit_list) # print "-"*79 # sys.exit() @@ -618,32 +642,40 @@ def print_bit_list_stats(bit_list): while True: print "_"*79 - bit_list, block_type, block_length = get_block_info(bitstream) + block_type, block_length = get_block_info(bitstream) + + print "*** block length:", block_length + try: block_type_name = BLOCK_TYPE_DICT[block_type] except KeyError: print "ERROR: Block type %s unknown in BLOCK_TYPE_DICT!" % hex(block_type) - print "Maybe 'even_odd' bool wrong???" print "-"*79 print "Debug bitlist:" - print_bitlist(bit_list) + print_bitlist(bitstream) print "-"*79 sys.exit(-1) print "*** block type: 0x%x (%s)" % (block_type, block_type_name) - print "*** block length:", block_length if block_type == EOF_BLOCK: print "EOF-Block found" break - bit_list, block_bit_list = pop_bytes_from_bit_list(bit_list, count=block_length) + if block_length == 0: + print "ERROR: block length == 0 ???" + print "-"*79 + print "Debug bitlist:" + print_bitlist(bitstream) + print "-"*79 + sys.exit(-1) -# print_block_table(block_bit_list) -# print_block_bit_list(block_bit_list) + block_bits = itertools.islice(bitstream, block_length) + block_codepoints = bitstream2codepoints(block_bits) +# print "".join([chr(i) for i in block_codepoints]) - cassette.add_block(block_type, block_length, block_bit_list) + cassette.add_block(block_type, block_length, block_codepoints) print "="*79 diff --git a/PyDC/utils.py b/PyDC/utils.py index cb5fbf84..40da123f 100755 --- a/PyDC/utils.py +++ b/PyDC/utils.py @@ -11,6 +11,7 @@ import collections import itertools import logging +import types LOG_FORMATTER = logging.Formatter("") # %(asctime)s %(message)s") @@ -178,68 +179,72 @@ def iter_window(g, window_size): yield list(values) -def count_continuous_pattern(bits, pattern): +def count_continuous_pattern(bitstream, pattern): """ - count 'pattern' matches without ceasing. - - >>> bit_str = ( - ... "00111100" - ... "00111100" - ... "0101") - >>> pos = count_continuous_pattern([int(i) for i in bit_str], "00111100") - >>> bit_str[pos*8:] - '0101' - >>> pos - 2 - - >>> count_continuous_pattern([1,1,1,2,3], "1") + >>> pattern = list(bytes2bit_strings("A")) + >>> bitstream = bytes2bit_strings("AAAXXX") + >>> count_continuous_pattern(bitstream, pattern) 3 - >>> count_continuous_pattern([1,2,3], "99") + >>> pattern = list(bytes2bit_strings("X")) + >>> bitstream = bytes2bit_strings("AAAXXX") + >>> count_continuous_pattern(bitstream, pattern) 0 - - >>> count_continuous_pattern([0,1,0,1], "01") - 2 """ - pattern_len = len(pattern) - pattern = [int(i) for i in pattern] - count=-1 - for count, data in enumerate(iter_steps(bits, pattern_len), 1): + assert isinstance(bitstream, (collections.Iterable, types.GeneratorType)) + assert isinstance(pattern, (list, tuple)) + + window_size = len(pattern) + count = -1 + for count, data in enumerate(iter_steps(bitstream, window_size), 1): +# print count, data, pattern if data != pattern: count -= 1 break + return count -def find_iter_window(bit_list, pattern): +class MaxPosArraived(Exception): + pass +class PatternNotFound(Exception): + pass + + +def find_iter_window(bitstream, pattern, max_pos=None): """ - Search for 'pattern' in bit-by-bit steps (iter window) - and return the number of bits before the 'pattern' match. + >>> pattern = list(bytes2bit_strings("B")) + >>> bitstream = bytes2bit_strings("AAABCCC") + >>> find_iter_window(bitstream, pattern) + 24 + >>> "".join(list(bitstream2string(bitstream))) + 'CCC' - Useable for slicing all bits before the first 'pattern' match: + >>> find_iter_window(bytes2bit_strings("HELLO!"), list(bytes2bit_strings("LO"))) + 24 - >>> bit_str = "111010111" - >>> pos = find_iter_window([int(i) for i in bit_str], "010") - >>> bit_str[pos:] - '010111' - >>> pos - 3 + >>> find_iter_window(bytes2bit_strings("HELLO!"), list(bytes2bit_strings("LO")), max_pos=16) + Traceback (most recent call last): + ... + MaxPosArraived: 17 - >>> find_iter_window([1,1,1], "0") - 0 - >>> find_iter_window([1,0,0], "1") - 0 - >>> find_iter_window([0,1,0], "1") - 1 - >>> find_iter_window([0,0,1], "1") - 2 - """ - pattern_len = len(pattern) - pattern = [int(i) for i in pattern] - for pos, data in enumerate(iter_window(bit_list, pattern_len)): + >>> find_iter_window(bytes2bit_strings("HELLO!"), list(bytes2bit_strings("X"))) + Traceback (most recent call last): + ... + PatternNotFound: 40 + """ + assert isinstance(bitstream, (collections.Iterable, types.GeneratorType)) + assert isinstance(pattern, (list, tuple)) + + window_size = len(pattern) + pos = -1 + for pos, data in enumerate(iter_window(bitstream, window_size)): +# print pos, data, pattern if data == pattern: return pos - return 0 + if max_pos is not None and pos > max_pos: + raise MaxPosArraived(pos) + raise PatternNotFound(pos) # def match_count(g, pattern): # """ @@ -250,7 +255,7 @@ def find_iter_window(bit_list, pattern): # print "Start leader '%s' found at position: %i" % (LEAD_IN_PATTERN, leader_pos) # # # Cut bits before the first 01010101 start leader -# print "bits before header:", repr(list2str(bit_list[:leader_pos])) +# print "bits before header:", repr(int_list2str(bit_list[:leader_pos])) # bit_list = bit_list[leader_pos:] # # # count lead-in byte matches without ceasing to get faster to the sync-byte @@ -308,32 +313,6 @@ def iter_pare_sum(data): yield (current[0], current[1] + next_value[1]) -def list2str(l): - """ - >>> list2str([0, 0, 0, 1, 0, 0, 1, 0]) - '00010010' - """ - return "".join([str(c) for c in l]) - -def print_block_bit_list(block_bit_list, display_block_count=8): - in_line_count = 0 - - line = "" - for no, block in enumerate(block_bit_list, -display_block_count + 1): - line += "%s " % list2str(block) - in_line_count += 1 - if in_line_count >= display_block_count: - in_line_count = 0 - print "%4s - %s" % (no, line) - line = "" - if in_line_count > 0: - print - -def print_bitlist(bit_list): - block_bit_list = iter_steps(bit_list, steps=8) - print_block_bit_list(block_bit_list) - - class TextLevelMeter(object): """ >>> tl = TextLevelMeter(255, 9) @@ -395,8 +374,268 @@ def count_sign(values, min_value): negative_count += 1 return positive_count, negative_count +def list2str(l): + return "".join([str(c) for c in l]) + +def bits2codepoint(bits): + """ + >>> c = bits2codepoint([0, 0, 0, 1, 0, 0, 1, 0]) + >>> c + 72 + >>> chr(c) + 'H' + + >>> bits2codepoint("00010010") + 72 + + >>> bits2codepoint([0, 0, 1, 1, 0, 0, 1, 0]) + 76 + """ + bit_string = "".join([str(c) for c in reversed(bits)]) + return int(bit_string, 2) + +def bitstream2codepoints(bitstream): + """ + >>> list(bitstream2codepoints([0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0])) + [72, 65] + + >>> [chr(i) for i in bitstream2codepoints(bytes2bitstream("HALLO!"))] + ['H', 'A', 'L', 'L', 'O', '!'] + """ + for bits in iter_steps(bitstream, 8): + yield bits2codepoint(bits) + + +def bits2string(bits): + """ + >>> bits2string([0, 0, 0, 1, 0, 0, 1, 0]) + 'H' + >>> bits2string("00010010") + 'H' + """ + codepoint = bits2codepoint(bits) + return chr(codepoint) + +def bitstream2string(bitstream): + """ + >>> list(bitstream2string([0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0])) + ['H', 'A'] + + >>> "".join(list(bitstream2string(bytes2bitstream("FooBar !")))) + 'FooBar !' + """ + for bits in iter_steps(bitstream, 8): + yield bits2string(bits) + +def byte2bit_string(data): + """ + >>> byte2bit_string("H") + '00010010' + """ + if isinstance(data, basestring): + assert len(data) == 1 + data = ord(data) + + bits = '{0:08b}'.format(data) + bits = bits[::-1] + return bits + + +def byte_list2bit_list(data): + """ + generator that yield a list + + >>> list(byte_list2bit_list("HELLO!")) + ['00010010', '10100010', '00110010', '00110010', '11110010', '10000100'] + + >>> data = (0x0,0x1e,0x8b,0x20,0x49,0x0) + >>> list(byte_list2bit_list(data)) + ['00000000', '01111000', '11010001', '00000100', '10010010', '00000000'] + """ + for char in data: + yield byte2bit_string(char) + + +def bytes2bit_strings(data): + """ + generator that yield a list + + >>> list(byte_list2bit_list("HELLO!")) + ['00010010', '10100010', '00110010', '00110010', '11110010', '10000100'] + + >>> data = (0x0,0x1e,0x8b,0x20,0x49,0x0) + >>> list(byte_list2bit_list(data)) + ['00000000', '01111000', '11010001', '00000100', '10010010', '00000000'] + """ + for char in data: + for bit in byte2bit_string(char): + yield bit + + +def bytes2bitstream(data): + """ + >>> list(bytes2bitstream("H")) + [0, 0, 0, 1, 0, 0, 1, 0] + + >>> list(bytes2bitstream("HA")) + [0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0] + """ + for bit_string in bytes2bit_strings(data): + yield int(bit_string) + + +def print_codepoint_stream(codepoint_stream, display_block_count=8, no_repr=False): + """ + >>> def g(txt): + ... for c in txt: yield ord(c) + >>> codepoint_stream = g("HELLO!") + >>> print_codepoint_stream(codepoint_stream) + ... # doctest: +NORMALIZE_WHITESPACE + 6 | 0x48 'H' | 0x45 'E' | 0x4c 'L' | 0x4c 'L' | 0x4f 'O' | 0x21 '!' | + """ + in_line_count = 0 + + line = [] + for no, codepoint in enumerate(codepoint_stream, 1): + r = repr(chr(codepoint)) + if "\\x" in r: # FIXME + txt = "%s" % hex(codepoint) + else: + txt = "%s %s" % (hex(codepoint), r) + + line.append(txt.center(8)) + + in_line_count += 1 + if in_line_count >= display_block_count: + in_line_count = 0 + print "%4s | %s |" % (no, " | ".join(line)) + line = [] + if line: + print "%4s | %s |" % (no, " | ".join(line)) + + if in_line_count > 0: + print + + +def print_block_bit_list(block_bit_list, display_block_count=8, no_repr=False): + """ + >>> bit_list = ( + ... [0,0,1,1,0,0,1,0], # L + ... [1,0,0,1,0,0,1,0], # I + ... ) + >>> print_block_bit_list(bit_list) + ... # doctest: +NORMALIZE_WHITESPACE + 2 - 00110010 10010010 + 0x4c 'L' 0x49 'I' + """ + def print_line(no, line, line_info): + print "%4s - %s" % (no, line) + if no_repr: + return + + line = [] + for codepoint in line_info: + r = repr(chr(codepoint)) + if "\\x" in r: # FIXME + txt = "%s" % hex(codepoint) + else: + txt = "%s %s" % (hex(codepoint), r) + txt = txt.center(8) + line.append(txt) + + print " %s" % " ".join(line) + + + in_line_count = 0 + + line = "" + line_info = [] + for no, bits in enumerate(block_bit_list, 1): + line += "%s " % "".join([str(c) for c in bits]) + + codepoint = bits2codepoint(bits) + line_info.append(codepoint) + + in_line_count += 1 + if in_line_count >= display_block_count: + in_line_count = 0 + print_line(no, line, line_info) + line_info = [] + line = "" + if line: + print_line(no, line, line_info) + + if in_line_count > 0: + print + +def print_bitlist(bitstream, no_repr=False): + """ + >>> bitstream = bytes2bitstream("Hallo World!") + >>> print_bitlist(bitstream) + ... # doctest: +NORMALIZE_WHITESPACE + 8 - 00010010 10000110 00110110 00110110 11110110 00000100 11101010 11110110 + 0x48 'H' 0x61 'a' 0x6c 'l' 0x6c 'l' 0x6f 'o' 0x20 ' ' 0x57 'W' 0x6f 'o' + 12 - 01001110 00110110 00100110 10000100 + 0x72 'r' 0x6c 'l' 0x64 'd' 0x21 '!' + + >>> bitstream = bytes2bitstream("Hallo World!") + >>> print_bitlist(bitstream, no_repr=True) + ... # doctest: +NORMALIZE_WHITESPACE + 8 - 00010010 10000110 00110110 00110110 11110110 00000100 11101010 11110110 + 12 - 01001110 00110110 00100110 10000100 + """ + block_bit_list = iter_steps(bitstream, steps=8) + print_block_bit_list(block_bit_list, no_repr=no_repr) + + +def get_word(byte_iterator): + """ + return a uint16 value + + >>> g=iter([0x1e, 0x12]) + >>> v=get_word(g) + >>> v + 7698 + >>> hex(v) + '0x1e12' + + >>> g=iter([0x0,0xf,0x20,0,20]) + >>> v=get_word(g) + >>> hex(v) + '0xf' + >>> v=get_word(g) + >>> v + 8192 + """ + return (next(byte_iterator) << 8) | next(byte_iterator) + + if __name__ == "__main__": + + data = (0xff,) + print list(byte_list2bit_list(data)) + + + txt = ( + "00000000" + "11110000" + "00000100" + "00000100" + ) + bitlist = [int(i) for i in txt] + print list(bitstream2codepoints(bitlist)) + print_bitlist(bitlist) +# + + g = iter([0x0, 0xf, 0x20, 0, 20]) + g = iter([0xf, 0x0, 0x20, 0, 20]) + v = get_word(g) + print v, repr(v), hex(v) + + sys.exit() + + import doctest print doctest.testmod() diff --git a/PyDC/wave2bitstream.py b/PyDC/wave2bitstream.py index cd0f4d3b..3ca9ba7e 100644 --- a/PyDC/wave2bitstream.py +++ b/PyDC/wave2bitstream.py @@ -21,8 +21,8 @@ audioop = None # own modules -from utils import average, diff_info, print_bitlist, TextLevelMeter, iter_window, \ - human_duration, ProcessInfo, LOG_LEVEL_DICT, LOG_FORMATTER +from utils import average, diff_info, TextLevelMeter, iter_window, \ + human_duration, ProcessInfo, LOG_LEVEL_DICT, LOG_FORMATTER, print_bitlist import struct import time @@ -354,15 +354,26 @@ def iter_wave_values(self): frame_no = 0 get_wave_block_func = functools.partial(self.wavefile.readframes, self.WAVE_READ_SIZE) skipped_values = 0 + + manually_audioop_bias = self.samplewidth == 1 and audioop is None + for frames in iter(get_wave_block_func, ""): - if audioop is not None and self.samplewidth == 1: - # 8 bit samples are unsigned, see: - # http://docs.python.org/2/library/audioop.html#audioop.lin2lin - frames = audioop.bias(frames, 1, 128) + if self.samplewidth == 1: + if audioop is None: + log.warning("use audioop.bias() work-a-round for missing audioop.") + else: + # 8 bit samples are unsigned, see: + # http://docs.python.org/2/library/audioop.html#audioop.lin2lin + frames = audioop.bias(frames, 1, 128) for value in array.array(typecode, frames): + if manually_audioop_bias: + # audioop.bias can't be used. + # See: http://hg.python.org/cpython/file/482590320549/Modules/audioop.c#l957 + value = value % 0xff - 128 + if abs(value) < self.min_volume: # Ignore to lower amplitude skipped_values += 1 @@ -372,17 +383,17 @@ def iter_wave_values(self): skipped_values = 0 msg = tlm.feed(value) - if log.level>=logging.DEBUG: - #~ try: + if log.level >= logging.DEBUG: + # ~ try: percent = 100.0 / self.max_value * abs(value) - #~ except + # ~ except log.debug( "%s value: %i (%.1f%%)" % (msg, value, percent) ) frame_no += 1 - #~ if frame_no>100:sys.exit() +# if frame_no > 100:sys.exit() yield frame_no, value # def iter_wave_valuesOLD(self): @@ -420,28 +431,28 @@ def iter_wave_values(self): ) # sys.exit() - #~ FILENAME = "HelloWorld1 xroar.wav" # 8Bit 22050Hz - FILENAME = "HelloWorld1 origin.wav" # 109922 frames, 16Bit wave, 44100Hz - #~ FILENAME = "LineNumber Test 01.wav" # tokenized BASIC + FILENAME = "HelloWorld1 xroar.wav" # 8Bit 22050Hz +# FILENAME = "HelloWorld1 origin.wav" # 109922 frames, 16Bit wave, 44100Hz + # ~ FILENAME = "LineNumber Test 01.wav" # tokenized BASIC - log_level = LOG_LEVEL_DICT[3] # args.verbosity - log.setLevel(log_level) - - logfilename = FILENAME + ".log" # args.logfile - if logfilename: - print "Log into '%s'" % logfilename - handler = logging.FileHandler(logfilename, - # mode='a', - mode='w', - encoding="utf8" - ) - handler.setFormatter(LOG_FORMATTER) - log.addHandler(handler) +# log_level = LOG_LEVEL_DICT[3] # args.verbosity +# log.setLevel(log_level) +# +# logfilename = FILENAME + ".log" # args.logfile +# if logfilename: +# print "Log into '%s'" % logfilename +# handler = logging.FileHandler(logfilename, +# # mode='a', +# mode='w', +# encoding="utf8" +# ) +# handler.setFormatter(LOG_FORMATTER) +# log.addHandler(handler) # if args.stdout_log: - #~ handler = logging.StreamHandler() - #~ handler.setFormatter(LOG_FORMATTER) - #~ log.addHandler(handler) +# handler = logging.StreamHandler() +# handler.setFormatter(LOG_FORMATTER) +# log.addHandler(handler) st = Wave2Bitstream(FILENAME, bit_nul_hz=1200, # "0" is a single cycle at 1200 Hz @@ -455,10 +466,10 @@ def iter_wave_values(self): bitstream.sync(32) bitstream = itertools.imap(lambda x: x[1], bitstream) - bit_list = array.array('B', bitstream) +# bit_list = array.array('B', bitstream) print "-"*79 - print_bitlist(bit_list) + print_bitlist(bitstream, no_repr=True) print "-"*79 From 1684a41dd39c0d1e9652533519336a25db56ce8c Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Mon, 26 Aug 2013 15:33:58 +0200 Subject: [PATCH 034/151] Works as stream processing, but only with "HelloWorld1 xroar.wav", yet. --- PyDC/PyDC.py | 199 ++++++++++++++++++++++++-------------------------- PyDC/utils.py | 53 ++++++-------- 2 files changed, 116 insertions(+), 136 deletions(-) diff --git a/PyDC/PyDC.py b/PyDC/PyDC.py index 0bdb3096..141e2b95 100755 --- a/PyDC/PyDC.py +++ b/PyDC/PyDC.py @@ -30,7 +30,8 @@ from basic_tokens import BASIC_TOKENS, FUNCTION_TOKEN from utils import find_iter_window, iter_steps, MaxPosArraived, \ print_bitlist, bits2codepoint, list2str, bitstream2codepoints, get_word, \ - print_codepoint_stream + print_codepoint_stream, codepoints2string, print_as_hex_list, \ + PatternNotFound from wave2bitstream import Wave2Bitstream @@ -115,115 +116,69 @@ def print_as_hex(block_codepoints): print line -def print_as_hex_list(block_codepoints): - line = [] - for block in block_codepoints: - byte_no = bits2codepoint(block) - character = chr(byte_no) - line.append(hex(byte_no)) - print ",".join(line) - - - LEADER_MAX_POS = 8 * 256 # Max search window for get the Leader-Byte? -def get_block_info(bitstream): -# bitstream = iter(itertools.islice(bitstream, 2500)) # TEST -# for i in xrange(119): -# next(bitstream) -# print "-"*79 -# # print_bitlist(bitstream, no_repr=True) -# print_bitlist(bitstream) -# print "-"*79 -# sys.exit() - - """ - 00111100 - 00000000 - 11110000 - 00000100 - 00000100 - - - 240 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 - 248 - 00111100 00000000 11110000 00000100 00000100 00000100 00000100 00000100 - 256 - 00000100 00000100 00000100 00000000 00000000 00000000 00000000 00000000 - 264 - 00000000 00000000 11110000 10101010 10101010 10101010 10101010 10101010 - - 240 - 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 - 0x55 'U' 0x55 'U' 0x55 'U' 0x55 'U' 0x55 'U' 0x55 'U' 0x55 'U' 0x55 'U' - 248 - 00111100 00000000 11110000 00000100 00000100 00000100 00000100 00000100 - 0x3c '<' 0x0 0xf 0x20 ' ' 0x20 ' ' 0x20 ' ' 0x20 ' ' 0x20 ' ' - 256 - 00000100 00000100 00000100 00000000 00000000 00000000 00000000 00000000 - 0x20 ' ' 0x20 ' ' 0x20 ' ' 0x0 0x0 0x0 0x0 0x0 - 264 - 00000000 00000000 11110000 10101010 10101010 10101010 10101010 10101010 - 0x0 0x0 0xf 0x55 'U' 0x55 'U' 0x55 'U' 0x55 'U' 0x55 'U' - """ +def sync_stream(bitstream): + try: + sync_pos = find_iter_window(bitstream, SYNC_BYTE, LEADER_MAX_POS) + except MaxPosArraived, err: + print "\nError: Sync-Byte not found in the first %i Bytes! (%s)" % ( + LEADER_MAX_POS, err + ) + sys.exit(-1) + else: + print "\nSync-Byte '%s' found at %i Bytes" % (list2str(SYNC_BYTE), sync_pos) - # Searching for lead-in byte -# try: -# -# leader_pos = find_iter_window(bitstream, LEAD_IN_PATTERN, LEADER_MAX_POS) -# except MaxPosArraived, err: -# print "\nError: Leader-Byte not found in the first %i Bytes! (%s)" % ( -# LEADER_MAX_POS, err -# ) -# sys.exit(-1) -# else: -# print "\nLeader-Byte '%s' found at %i Bytes" % (list2str(LEAD_IN_PATTERN), leader_pos) +def get_block_info(bitstream): # print "-"*79 +# bitstream = list(bitstream) # print_bitlist(bitstream, no_repr=True) +# bitstream = iter(bitstream) # print "-"*79 -# sys.exit() + # Searching for lead-in byte try: - sync_pos = find_iter_window(bitstream, SYNC_BYTE, LEADER_MAX_POS) + leader_pos = find_iter_window(bitstream, LEAD_IN_PATTERN, LEADER_MAX_POS) except MaxPosArraived, err: - print "\nError: Sync-Byte not found in the first %i Bytes! (%s)" % ( + print "\nError: Leader-Byte not found in the first %i Bytes! (%s)" % ( LEADER_MAX_POS, err ) sys.exit(-1) + except PatternNotFound, err: + print "\nError: Leader-Byte doesn't exist in bitstream! (%s)" % err + sys.exit(-1) else: - print "\nSync-Byte '%s' found at %i Bytes" % (list2str(SYNC_BYTE), sync_pos) + print "\nLeader-Byte '%s' found at %i Bytes" % (list2str(LEAD_IN_PATTERN), leader_pos) # print "-"*79 # print_bitlist(bitstream, no_repr=True) # print "-"*79 # sys.exit() - """ - 00111100 00000000 111100000 00001000 00001000 00001000 00001000 00001000 00001000 00001000 - - 00111100 - 00000000 - 11110000 - 00000100 - 00000100 - - 0 00001000 00001000 00001000 00001000 00001000 - """ + sync_stream(bitstream) # Sync bitstream with SYNC_BYTE # print "-"*79 +# bitstream = list(bitstream) # print_bitlist(bitstream) +# bitstream = iter(bitstream) # print "-"*79 -# sys.exit() codepoint_stream = bitstream2codepoints(bitstream) # print "-"*79 # print_codepoint_stream(codepoint_stream) # print "-"*79 - block_type = get_word(codepoint_stream) - print "raw block type:", repr(block_type), hex(block_type) - block_length = get_word(codepoint_stream) - print "raw block length:", repr(block_length) + block_type = next(codepoint_stream) +# print "raw block type:", repr(block_type), hex(block_type) + block_length = next(codepoint_stream) +# print "raw block length:", repr(block_length) -# sys.exit() + codepoint_stream = itertools.islice(codepoint_stream, block_length * 8) - return block_type, block_length + return block_type, block_length, codepoint_stream @@ -347,17 +302,36 @@ def add_block_data(self, block_length, data): 32768 PRINT 32768 63999 PRINT "END";63999 """ + +# data = list(data) +# # print repr(data) +# print_as_hex_list(data) +# print_codepoint_stream(data) +# data = iter(data) +# sys.exit() + byte_count = 0 while True: line_pointer = get_word(data) +# print "line_pointer:", repr(line_pointer) byte_count += 2 if not line_pointer: # arrived [0x00, 0x00] -> end of block break - line_number = get_word(data) + try: + line_number = get_word(data) + except StopIteration: + print "No line number anymore" + break +# print "line_number:", repr(line_number) byte_count += 2 +# data = list(data) +# print_as_hex_list(data) +# print_codepoint_stream(data) +# data = iter(data) + # get the code line: # new iterator to get all characters until 0x00 arraived code = iter(data.next, 0x00) @@ -365,6 +339,9 @@ def add_block_data(self, block_length, data): code = list(code) # for len() byte_count += len(code) + 1 # from 0x00 consumed in iter() +# print_as_hex_list(code) +# print_codepoint_stream(code) + # convert to a plain ASCII string code = bytes2codeline(code) @@ -449,17 +426,18 @@ class CassetteFile(object): 5.6 Two bytes for the default load address of a binary file. """ - def __init__(self, file_block_data): -# print_block_codepoints(block_codepoints) - print_block_table(block_codepoints) - print_as_hex_list(file_block_data) + def __init__(self, block_codepoints): + + raw_filename = list(itertools.islice(block_codepoints, 8)) + self.filename = codepoints2string(raw_filename) + print "Filename: %s (%s)" % (repr(self.filename), raw_filename) - self.filename = bit_blocks2string(block_codepoints[:8]) + codepoints = list(block_codepoints) - byte_no_block = bit_blocks2codepoint(file_block_data[8:]) - print "file meta:", repr(byte_no_block) +# print "file meta:" +# print_codepoint_stream(codepoints) - self.file_type = byte_no_block[0] + self.file_type = codepoints[0] print "file type:", repr(self.file_type) if self.file_type == 0x00: print "BASIC programm (0x00)" @@ -474,7 +452,7 @@ def __init__(self, file_block_data): "Unknown file type %s is not supported, yet." % hex(self.file_type) ) - ascii_flag = byte_no_block[1] + ascii_flag = codepoints[1] print "ASCII Flag is:", repr(ascii_flag) if ascii_flag == 0x00: print "tokenized BASIC" @@ -483,17 +461,13 @@ def __init__(self, file_block_data): print "ASCII BASIC" self.is_tokenized = False - self.file_content = FileContent() - def add_block_data(self, block_length, block_codepoints): - print "raw data length: %iBytes" % len(block_codepoints) -# print_as_hex_list(block_codepoints) - data = iter([bits2codepoint(bit_block) for bit_block in block_codepoints]) + def add_block_data(self, block_length, codepoints): if self.is_tokenized: - self.file_content.add_block_data(block_length, data) + self.file_content.add_block_data(block_length, codepoints) else: - self.file_content.add_ascii_block(block_length, data) + self.file_content.add_ascii_block(block_length, codepoints) print "*"*79 self.file_content.print_code_lines() print "*"*79 @@ -551,7 +525,7 @@ def print_bit_list_stats(bit_list): # created by Xroar Emulator -# FILENAME = "HelloWorld1 xroar.wav" # 8Bit 22050Hz + FILENAME = "HelloWorld1 xroar.wav" # 8Bit 22050Hz # Bit 1 min: 1696Hz avg: 2058.3Hz max: 2205Hz variation: 509Hz # Bit 0 min: 595Hz avg: 1090.4Hz max: 1160Hz Variation: 565Hz # 4760 Bits: 2243 positive bits and 2517 negative bits @@ -560,7 +534,7 @@ def print_bit_list_stats(bit_list): # created by origin Dragon 32 machine # 16Bit 44.1KHz mono - FILENAME = "HelloWorld1 origin.wav" # no sync neede +# FILENAME = "HelloWorld1 origin.wav" # no sync neede # Bit 1 min: 1764Hz avg: 2013.9Hz max: 2100Hz variation: 336Hz # Bit 0 min: 595Hz avg: 1090.2Hz max: 1336Hz Variation: 741Hz # 2710 Bits: 1217 positive bits and 1493 negative bits @@ -585,7 +559,7 @@ def print_bit_list_stats(bit_list): # FILENAME = "Quickbeam Software - Duplicas v3.0.wav" # binary! - # FILENAME = "Dragon Data Ltd - Examples from the Manual - 39~58 [run].wav" +# FILENAME = "Dragon Data Ltd - Examples from the Manual - 39~58 [run].wav" # Bit 1 min: 1696Hz avg: 2004.0Hz max: 2004Hz variation: 308Hz # Bit 0 min: 1025Hz avg: 1025.0Hz max: 1025Hz Variation: 0Hz # 155839 Bits: 73776 positive bits and 82063 negative bits @@ -631,18 +605,31 @@ def print_bit_list_stats(bit_list): # bit_list = array.array('B', bitstream) - # print "-"*79 - # print_bitlist(bit_list) - # print "-"*79 -# print_block_table(bit_list) +# print "-"*79 +# bitstream = list(bitstream) +# sync_stream(bitstream) # Sync bitstream with SYNC_BYTE +# print_bitlist(bitstream) +# bitstream = iter(bitstream) # print "-"*79 # sys.exit() +# bitstream = list(bitstream) +# print " ***** Bitstream length:", len(bitstream) +# bitstream = iter(bitstream) + cassette = Cassette() while True: print "_"*79 - block_type, block_length = get_block_info(bitstream) +# bitstream = list(bitstream) +# print " ***** Bitstream length:", len(bitstream) +# bitstream = iter(bitstream) + + block_type, block_length, codepoint_stream = get_block_info(bitstream) + +# codepoint_stream = list(codepoint_stream) +# print " ***** codepoint_stream length:", len(codepoint_stream) +# codepoint_stream = iter(codepoint_stream) print "*** block length:", block_length @@ -671,11 +658,13 @@ def print_bit_list_stats(bit_list): print "-"*79 sys.exit(-1) - block_bits = itertools.islice(bitstream, block_length) - block_codepoints = bitstream2codepoints(block_bits) -# print "".join([chr(i) for i in block_codepoints]) +# block_codepoints = bitstream2codepoints(block_bits) + +# block_codepoints = list(block_codepoints) +# print_codepoint_stream(block_codepoints) +# block_codepoints = iter(block_codepoints) - cassette.add_block(block_type, block_length, block_codepoints) + cassette.add_block(block_type, block_length, codepoint_stream) print "="*79 diff --git a/PyDC/utils.py b/PyDC/utils.py index 40da123f..7b295419 100755 --- a/PyDC/utils.py +++ b/PyDC/utils.py @@ -394,6 +394,7 @@ def bits2codepoint(bits): bit_string = "".join([str(c) for c in reversed(bits)]) return int(bit_string, 2) + def bitstream2codepoints(bitstream): """ >>> list(bitstream2codepoints([0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0])) @@ -416,6 +417,7 @@ def bits2string(bits): codepoint = bits2codepoint(bits) return chr(codepoint) + def bitstream2string(bitstream): """ >>> list(bitstream2string([0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0])) @@ -427,6 +429,7 @@ def bitstream2string(bitstream): for bits in iter_steps(bitstream, 8): yield bits2string(bits) + def byte2bit_string(data): """ >>> byte2bit_string("H") @@ -499,7 +502,7 @@ def print_codepoint_stream(codepoint_stream, display_block_count=8, no_repr=Fals for no, codepoint in enumerate(codepoint_stream, 1): r = repr(chr(codepoint)) if "\\x" in r: # FIXME - txt = "%s" % hex(codepoint) + txt = "%s %i" % (hex(codepoint), codepoint) else: txt = "%s %s" % (hex(codepoint), r) @@ -517,6 +520,14 @@ def print_codepoint_stream(codepoint_stream, display_block_count=8, no_repr=Fals print +def print_as_hex_list(codepoint_stream): + """ + >>> print_as_hex_list([70, 111, 111, 32, 66, 97, 114, 32, 33]) + 0x46,0x6f,0x6f,0x20,0x42,0x61,0x72,0x20,0x21 + """ + print ",".join([hex(codepoint) for codepoint in codepoint_stream]) + + def print_block_bit_list(block_bit_list, display_block_count=8, no_repr=False): """ >>> bit_list = ( @@ -598,42 +609,22 @@ def get_word(byte_iterator): 7698 >>> hex(v) '0x1e12' - - >>> g=iter([0x0,0xf,0x20,0,20]) - >>> v=get_word(g) - >>> hex(v) - '0xf' - >>> v=get_word(g) - >>> v - 8192 """ return (next(byte_iterator) << 8) | next(byte_iterator) +def codepoints2string(codepoints): + """ + >>> codepoints = [ord(c) for c in "Foo Bar !"] + >>> codepoints + [70, 111, 111, 32, 66, 97, 114, 32, 33] + >>> codepoints2string(codepoints) + 'Foo Bar !' + """ + return "".join([chr(c) for c in codepoints]) if __name__ == "__main__": - - data = (0xff,) - print list(byte_list2bit_list(data)) - - - txt = ( - "00000000" - "11110000" - "00000100" - "00000100" - ) - bitlist = [int(i) for i in txt] - print list(bitstream2codepoints(bitlist)) - print_bitlist(bitlist) -# - - g = iter([0x0, 0xf, 0x20, 0, 20]) - g = iter([0xf, 0x0, 0x20, 0, 20]) - v = get_word(g) - print v, repr(v), hex(v) - - sys.exit() +# sys.exit() import doctest From 2124ff46e8667ea6cfd2898e023ce88f4dd87bac Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Mon, 26 Aug 2013 21:39:44 +0200 Subject: [PATCH 035/151] Many bugfixes. Start implementing code for BAS2WAV, but now only for debugging. --- PyDC/CassetteObjects.py | 538 ++++++++++++++++ PyDC/PyDC.py | 592 +++++------------- PyDC/basic_tokens.py | 36 ++ PyDC/configs.py | 105 ++++ ...Examples from the Manual - 39~58 [run].bas | 9 + ...Examples from the Manual - 39~58 [run].wav | Bin 0 -> 5151847 bytes PyDC/{ => test_files}/HelloWorld1 origin.wav | Bin PyDC/{ => test_files}/HelloWorld1 xroar.wav | Bin PyDC/test_files/HelloWorld1.bas | 3 + PyDC/{ => test_files}/LineNumber Test 01.wav | Bin PyDC/{ => test_files}/LineNumber Test 02.wav | Bin PyDC/test_files/LineNumberTest.bas | 6 + PyDC/utils.py | 48 ++ PyDC/wave2bitstream.py | 20 +- 14 files changed, 899 insertions(+), 458 deletions(-) create mode 100644 PyDC/CassetteObjects.py create mode 100644 PyDC/configs.py create mode 100755 PyDC/test_files/Dragon Data Ltd - Examples from the Manual - 39~58 [run].bas create mode 100755 PyDC/test_files/Dragon Data Ltd - Examples from the Manual - 39~58 [run].wav rename PyDC/{ => test_files}/HelloWorld1 origin.wav (100%) rename PyDC/{ => test_files}/HelloWorld1 xroar.wav (100%) create mode 100755 PyDC/test_files/HelloWorld1.bas rename PyDC/{ => test_files}/LineNumber Test 01.wav (100%) rename PyDC/{ => test_files}/LineNumber Test 02.wav (100%) create mode 100755 PyDC/test_files/LineNumberTest.bas diff --git a/PyDC/CassetteObjects.py b/PyDC/CassetteObjects.py new file mode 100644 index 00000000..7ccb0711 --- /dev/null +++ b/PyDC/CassetteObjects.py @@ -0,0 +1,538 @@ +#!/usr/bin/env python2 +# coding: utf-8 + +""" + PyDC - Cassette Objects + ======================= + + Python objects to hold the content of a Cassette. + + :copyleft: 2013 by Jens Diemer + :license: GNU GPL v3 or above, see LICENSE for more details. +""" + +import itertools +import logging +import os + +# own modules +from basic_tokens import bytes2codeline +from configs import Dragon32Config +from utils import get_word, codepoints2string, string2codepoint, LOG_LEVEL_DICT, \ + LOG_FORMATTER, codepoints2bitstream, pprint_codepoints +import sys + + +log = logging.getLogger("PyDC") + + +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 get_as_codepoints(self): + return string2codepoint("%i %s" % (self.line_no, self.code)) + + def __repr__(self): + return "" % ( + repr(self.line_pointer), repr(self.line_no), repr(self.code) + ) + + +class FileContent(object): + """ + Content (all data blocks) of a cassette file. + """ + def __init__(self, cfg): + self.cfg = cfg + self.code_lines = [] + + def create_from_bas(self, file_content): + for line in file_content.splitlines(): + line_number, code = line.split(" ", 1) + line_number = int(line_number) + self.code_lines.append( + CodeLine(None, line_number, code) + ) + + def add_block_data(self, block_length, data): + """ + add a block of tokenized BASIC source code lines. + + >>> cfg = Dragon32Config + >>> fc = FileContent(cfg) + + >>> block = [ + ... 0x1e,0x12,0x0,0xa,0x80,0x20,0x49,0x20,0xcb,0x20,0x31,0x20,0xbc,0x20,0x31,0x30,0x0, + ... 0x0,0x0] + >>> len(block) + 19 + >>> fc.add_block_data(19,iter(block)) + 19 Bytes parsed + >>> fc.print_code_lines() + 10 FOR I = 1 TO 10 + + >>> block = iter([ + ... 0x1e,0x29,0x0,0x14,0x87,0x20,0x49,0x3b,0x22,0x48,0x45,0x4c,0x4c,0x4f,0x20,0x57,0x4f,0x52,0x4c,0x44,0x21,0x22,0x0, + ... 0x0,0x0]) + >>> fc.add_block_data(999,block) + 25 Bytes parsed + ERROR: Block length value 999 is not equal to parsed bytes! + >>> fc.print_code_lines() + 10 FOR I = 1 TO 10 + 20 PRINT I;"HELLO WORLD!" + + >>> block = iter([ + ... 0x1e,0x31,0x0,0x1e,0x8b,0x20,0x49,0x0, + ... 0x0,0x0]) + >>> fc.add_block_data(10,block) + 10 Bytes parsed + >>> fc.print_code_lines() + 10 FOR I = 1 TO 10 + 20 PRINT I;"HELLO WORLD!" + 30 NEXT I + + + Test function tokens in code + + >>> fc = FileContent(cfg) + >>> data = iter([ + ... 0x1e,0x4a,0x0,0x1e,0x58,0xcb,0x58,0xc3,0x4c,0xc5,0xff,0x88,0x28,0x52,0x29,0x3a,0x59,0xcb,0x59,0xc3,0x4c,0xc5,0xff,0x89,0x28,0x52,0x29,0x0, + ... 0x0,0x0 + ... ]) + >>> fc.add_block_data(30, data) + 30 Bytes parsed + >>> fc.print_code_lines() + 30 X=X+L*SIN(R):Y=Y+L*COS(R) + + + Test high line numbers + + >>> fc = FileContent(cfg) + >>> 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 + ... ] + >>> len(data) + 99 + >>> fc.add_block_data(99, iter(data)) + 99 Bytes parsed + >>> fc.print_code_lines() + 1 PRINT "LINE NUMBER TEST" + 10 PRINT 10 + 100 PRINT 100 + 1000 PRINT 1000 + 10000 PRINT 10000 + 32768 PRINT 32768 + 63999 PRINT "END";63999 + """ + +# data = list(data) +# # print repr(data) +# print_as_hex_list(data) +# print_codepoint_stream(data) +# data = iter(data) +# sys.exit() + + byte_count = 0 + while True: + try: + line_pointer = get_word(data) + except StopIteration: + print "No line pointer information in code line data." + break +# print "line_pointer:", repr(line_pointer) + byte_count += 2 + if not line_pointer: + # arrived [0x00, 0x00] -> end of block + break + + try: + line_number = get_word(data) + except StopIteration: + print "No line number information in code line data." + break +# print "line_number:", repr(line_number) + byte_count += 2 + +# data = list(data) +# print_as_hex_list(data) +# print_codepoint_stream(data) +# data = iter(data) + + # get the code line: + # new iterator to get all characters until 0x00 arraived + code = iter(data.next, 0x00) + + code = list(code) # for len() + byte_count += len(code) + 1 # from 0x00 consumed in iter() + +# print_as_hex_list(code) +# print_codepoint_stream(code) + + # convert to a plain ASCII string + code = bytes2codeline(code) + + self.code_lines.append( + CodeLine(line_pointer, line_number, code) + ) + + print "%i Bytes parsed" % byte_count + if block_length != byte_count: + print "ERROR: Block length value %i is not equal to parsed bytes!" % block_length + + def add_ascii_block(self, block_length, data): + """ + add a block of ASCII BASIC source code lines. + + >>> data = [ + ... 0xd, + ... 0x31,0x30,0x20,0x50,0x52,0x49,0x4e,0x54,0x20,0x22,0x54,0x45,0x53,0x54,0x22, + ... 0xd, + ... 0x32,0x30,0x20,0x50,0x52,0x49,0x4e,0x54,0x20,0x22,0x48,0x45,0x4c,0x4c,0x4f,0x20,0x57,0x4f,0x52,0x4c,0x44,0x21,0x22, + ... 0xd + ... ] + >>> len(data) + 41 + >>> fc = FileContent(Dragon32Config) + >>> fc.add_ascii_block(41, iter(data)) + 41 Bytes parsed + >>> fc.print_code_lines() + 10 PRINT "TEST" + 20 PRINT "HELLO WORLD!" + """ + data.next() # Skip first \r + byte_count = 1 # incl. first \r + while True: + code = iter(data.next, 0xd) # until \r + code = "".join([chr(c) for c in code]) + if not code: + break + + byte_count += len(code) + 1 # and \r consumed in iter() + + try: + line_number, code = code.split(" ", 1) + except ValueError, err: + print "\nERROR: Splitting linenumber in %s: %s" % (repr(code), err) + break + + try: + line_number = int(line_number) + except ValueError, err: + print "\nERROR: Part '%s' is not a line number!" % repr(line_number) + continue + + self.code_lines.append( + CodeLine(None, line_number, code) + ) + + print "%i Bytes parsed" % byte_count + if block_length != byte_count: + print "\nERROR: Block length value %i is not equal to parsed bytes!" % block_length + + def get_as_codepoints(self): + result = [] + delim = list(string2codepoint("\r"))[0] + for codepoints in self.code_lines: + result.append(delim) + result += list(codepoints.get_as_codepoints()) + result.append(delim) + + result += self.cfg.BASIC_CODE_END + log.debug("code: %s" % repr(result)) + return result + + def print_code_lines(self): + for code_line in self.code_lines: + print "%i %s" % (code_line.line_no, code_line.code) + + def print_debug_info(self): + print "\tcode lines:" + print "-"*79 + self.print_code_lines() + print "-"*79 + + +class CassetteFile(object): + """ + Representes a "file name block" and his "data block" + + 5.1 An 8 byte program name + 5.2 A file ID byte where: + 00=BASIC program + 01=Data file + 03=Binary file + 5.3 An ASCII flag where: + 00=Binary file + FF=ASCII file + 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. + 5.5 Two bytes for the default EXEC address + of a binary file. + 5.6 Two bytes for the default load address + of a binary file. + """ + def __init__(self, cfg): + self.cfg = cfg + self.is_tokenized = False + + def create_from_bas(self, filename, file_content): + filename2 = os.path.split(filename)[1] + filename2 = filename2.upper() + filename2 = filename2.replace(" ", "_") + # TODO: remove non ASCII! + filename2 = filename2[:8].ljust(8, " ") + + log.debug("filename '%s' from: %s" % (filename2, filename)) + + self.filename = filename2 + self.file_type = self.cfg.FTYPE_BASIC # BASIC programm (0x00) + self.file_content = FileContent(self.cfg) + self.file_content.create_from_bas(file_content) + + def create_from_wave(self, block_codepoints): + + block_codepoints = list(block_codepoints) + print "filename data:", + pprint_codepoints(block_codepoints) + block_codepoints = iter(block_codepoints) + + raw_filename = list(itertools.islice(block_codepoints, 8)) + + self.filename = codepoints2string(raw_filename) + print "\nFilename: %s" % repr(self.filename) + + codepoints = list(block_codepoints) + +# print "file meta:" +# print_codepoint_stream(codepoints) + + self.file_type = codepoints[0] + + if not self.file_type in self.cfg.FILETYPE_DICT: + raise NotImplementedError( + "Unknown file type %s is not supported, yet." % hex(self.file_type) + ) + + print "file type:", self.cfg.FILETYPE_DICT[self.file_type] + + if self.file_type == self.cfg.FTYPE_DATA: + raise NotImplementedError("Data files are not supported, yet.") + elif self.file_type == self.cfg.FTYPE_BIN: + raise NotImplementedError("Binary files are not supported, yet.") + + ascii_flag = codepoints[1] + print "ASCII Flag is:", repr(ascii_flag) + if ascii_flag == self.cfg.BASIC_TOKENIZED: + self.is_tokenized = True + elif ascii_flag == self.cfg.BASIC_ASCII: + self.is_tokenized = False + else: + raise NotImplementedError("Unknown BASIC type: '%s'" % hex(ascii_flag)) + + print "code type is:", self.cfg.BASIC_TYPE_DICT[ascii_flag] + + self.file_content = FileContent(self.cfg) + + def add_block_data(self, block_length, codepoints): + if self.is_tokenized: + self.file_content.add_block_data(block_length, codepoints) + else: + self.file_content.add_ascii_block(block_length, codepoints) + print "*"*79 + self.file_content.print_code_lines() + print "*"*79 + + def get_filename_block_as_codepoints(self): + codepoints = [] + codepoints += list(string2codepoint(self.filename)) + codepoints.append(self.cfg.FTYPE_BASIC) + codepoints.append(self.cfg.BASIC_ASCII) # ASCII BASIC + return codepoints + + def get_code_block_as_codepoints(self): + return self.file_content.get_as_codepoints() + + def print_debug_info(self): + print "\tFilename: '%s'" % self.filename + print "\tfile type: %s" % self.cfg.FILETYPE_DICT[self.file_type] + print "\tis tokenized:", self.is_tokenized + self.file_content.print_debug_info() + + def __repr__(self): + return "" % (self.filename,) + + +class Cassette(object): + """ + >>> d32cfg = Dragon32Config() + >>> c = Cassette(d32cfg) + >>> c.add_from_bas("test_files/HelloWorld1.bas") + >>> c.print_debug_info() # doctest: +NORMALIZE_WHITESPACE + There exists 1 files: + Filename: 'HELLOWOR' + file type: BASIC programm (0x00) + is tokenized: False + code lines: + ------------------------------------------------------------------------------- + 10 FOR I = 1 TO 10 + 20 PRINT I;"HELLO WORLD!" + 30 NEXT I + ------------------------------------------------------------------------------- + >>> c.pprint_codepoint_stream() + 255 x LEAD_BYTE_CODEPOINT + 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 0x55 + 1x SYNC_BYTE_CODEPOINT + 0x3c + block type filename block (0x00) + 0x0 + block length: 0xa + 0xa + yield block data + 0x48 0x45 0x4c 0x4c 0x4f 0x57 0x4f 0x52 0x0 0xff + block type data block (0x01) + 0x1 + block length: 0x36 + 0x36 + yield block data + 0x31 0x30 0x20 0x46 0x4f 0x52 0x20 0x49 0x20 0x3d 0x20 0x31 0x20 0x54 0x4f 0x20 0x31 0x30 0x32 0x30 0x20 0x50 0x52 0x49 0x4e 0x54 0x20 0x49 0x3b 0x22 0x48 0x45 0x4c 0x4c 0x4f 0x20 0x57 0x4f 0x52 0x4c 0x44 0x21 0x22 0x33 0x30 0x20 0x4e 0x45 0x58 0x54 0x20 0x49 0x0 0x0 + block type end-of-file block (0xff) + 0xff + block length: 0x0 + 0x0 + """ + def __init__(self, cfg): + self.cfg = cfg + self.files = [] + self.current_file = None + + def add_from_bas(self, filename): + with open(filename, "r") as f: + file_content = f.read() + + self.current_file = CassetteFile(self.cfg) + self.current_file.create_from_bas(filename, file_content) + self.files.append(self.current_file) + + def add_block(self, block_type, block_length, block_codepoints): + if block_type == self.cfg.EOF_BLOCK: + return + elif block_type == self.cfg.FILENAME_BLOCK: + self.current_file = CassetteFile(self.cfg) + self.current_file.create_from_wave(block_codepoints) + print "Add file %s" % repr(self.current_file) + self.files.append(self.current_file) + elif block_type == self.cfg.DATA_BLOCK: + self.current_file.add_block_data(block_length, block_codepoints) + else: + raise TypeError("Block type %s unkown!" & hex(block_type)) + + def print_debug_info(self): + print "There exists %s files:" % len(self.files) + for file_obj in self.files: + file_obj.print_debug_info() + + def block2codepoint_stream(self, block_type, block_codepoints): + log.debug("%s x LEAD_BYTE_CODEPOINT" % self.cfg.LEAD_BYTE_LEN) + for count in xrange(self.cfg.LEAD_BYTE_LEN): + yield self.cfg.LEAD_BYTE_CODEPOINT + log.debug("\n1x SYNC_BYTE_CODEPOINT") + yield self.cfg.SYNC_BYTE_CODEPOINT + + log.debug("\nblock type is: '%s'" % self.cfg.BLOCK_TYPE_DICT[block_type]) + yield block_type + + block_length = len(block_codepoints) + log.debug("\nblock length: %s" % hex(block_length)) + yield block_length + + if block_codepoints: # not if EOF block + log.debug("\nyield %s" % self.cfg.BLOCK_TYPE_DICT[block_type]) + print "-"*79 + block_codepoints = list(block_codepoints) + pprint_codepoints(block_codepoints) + block_codepoints = iter(block_codepoints) + print "-"*79 + for codepoint in block_codepoints: + yield codepoint + + def codepoint_stream(self): + for file_obj in self.files: + # yield filename + for codepoints in self.block2codepoint_stream( + block_type=self.cfg.FILENAME_BLOCK, + block_codepoints=file_obj.get_filename_block_as_codepoints() + ): + yield codepoints + + # yield file content + for codepoints in self.block2codepoint_stream( + block_type=self.cfg.DATA_BLOCK, + block_codepoints=file_obj.get_code_block_as_codepoints() + ): + yield codepoints + + # yield EOF + for codepoints in self.block2codepoint_stream( + block_type=self.cfg.EOF_BLOCK, + block_codepoints=[] + ): + yield codepoints + + def get_as_bitstream(self): + for codepoint in self.codepoint_stream(): + assert isinstance(codepoint, int), "Codepoint %s is not int/hex" % repr(codepoint) + for bit in codepoints2bitstream(codepoint): + yield bit + + def pprint_codepoint_stream(self): + log_level = LOG_LEVEL_DICT[3] + log.setLevel(log_level) + + handler = logging.StreamHandler(stream=sys.stdout) + handler.setFormatter(LOG_FORMATTER) + log.addHandler(handler) + + for codepoint in self.codepoint_stream(): + try: + print hex(codepoint), + except TypeError, err: + raise TypeError( + "\n\nERROR with '%s': %s" % (repr(codepoint), err) + ) + + + +if __name__ == "__main__": + log_level = LOG_LEVEL_DICT[3] + log.setLevel(log_level) + + handler = logging.StreamHandler(stream=sys.stdout) + handler.setFormatter(LOG_FORMATTER) + log.addHandler(handler) + + d32cfg = Dragon32Config() + c = Cassette(d32cfg) + c.add_from_bas("test_files/HelloWorld1.bas") + c.print_debug_info() +# print list(c.codepoint_stream()) + print list(c.get_as_bitstream()) + + import doctest + print doctest.testmod( + verbose=False + # verbose=True + ) +# sys.exit() diff --git a/PyDC/PyDC.py b/PyDC/PyDC.py index 141e2b95..0295ddf5 100755 --- a/PyDC/PyDC.py +++ b/PyDC/PyDC.py @@ -22,6 +22,8 @@ import sys import itertools import logging +from configs import Dragon32Config +from CassetteObjects import Cassette log = logging.getLogger("PyDC") @@ -31,40 +33,13 @@ from utils import find_iter_window, iter_steps, MaxPosArraived, \ print_bitlist, bits2codepoint, list2str, bitstream2codepoints, get_word, \ print_codepoint_stream, codepoints2string, print_as_hex_list, \ - PatternNotFound + PatternNotFound, LOG_LEVEL_DICT, LOG_FORMATTER, codepoints2bitstream, \ + pprint_codepoints from wave2bitstream import Wave2Bitstream -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 ? - -# Normaly the Dragon LeadIn-Byte is 0x55: "10101010" -# but in worst-case the last null can consume the first -# null of the sync byte. -# We use a reversed version of the LeadIn-Byte to avoid this. -# LEAD_IN_PATTERN = "01010101" -LEAD_IN_PATTERN = [0, 1, 0, 1, 0, 1, 0, 1] -# LEAD_IN_PATTERN = "10101010" # 0x55 - -# SYNC_BYTE = "00111100" # 0x3C -SYNC_BYTE = [0, 0, 1, 1, 1, 1, 0, 0] # 0x3C - -# Block types: -FILENAME_BLOCK = 0x00 -DATA_BLOCK = 0x01 -EOF_BLOCK = 0xff - -BLOCK_TYPE_DICT = { - FILENAME_BLOCK: "filename block", - DATA_BLOCK: "data block", - EOF_BLOCK: "end-of-file block", -} - DISPLAY_BLOCK_COUNT = 8 # How many bit block should be printet in one line? - - MIN_SAMPLE_VALUE = 5 @@ -117,381 +92,143 @@ def print_as_hex(block_codepoints): -LEADER_MAX_POS = 8 * 256 # Max search window for get the Leader-Byte? - - -def sync_stream(bitstream): - try: - sync_pos = find_iter_window(bitstream, SYNC_BYTE, LEADER_MAX_POS) - except MaxPosArraived, err: - print "\nError: Sync-Byte not found in the first %i Bytes! (%s)" % ( - LEADER_MAX_POS, err - ) - sys.exit(-1) - else: - print "\nSync-Byte '%s' found at %i Bytes" % (list2str(SYNC_BYTE), sync_pos) - +class BitstreamHandler(object): + def __init__(self, cfg): + self.cfg = cfg + self.cassette = Cassette(cfg) -def get_block_info(bitstream): -# print "-"*79 -# bitstream = list(bitstream) -# print_bitlist(bitstream, no_repr=True) -# bitstream = iter(bitstream) -# print "-"*79 - - # Searching for lead-in byte - try: - leader_pos = find_iter_window(bitstream, LEAD_IN_PATTERN, LEADER_MAX_POS) - except MaxPosArraived, err: - print "\nError: Leader-Byte not found in the first %i Bytes! (%s)" % ( - LEADER_MAX_POS, err - ) - sys.exit(-1) - except PatternNotFound, err: - print "\nError: Leader-Byte doesn't exist in bitstream! (%s)" % err - sys.exit(-1) - else: - print "\nLeader-Byte '%s' found at %i Bytes" % (list2str(LEAD_IN_PATTERN), leader_pos) - -# print "-"*79 -# print_bitlist(bitstream, no_repr=True) -# print "-"*79 -# sys.exit() - - sync_stream(bitstream) # Sync bitstream with SYNC_BYTE + def feed(self, bitstream): + while True: + print "_"*79 + # bitstream = list(bitstream) + # print " ***** Bitstream length:", len(bitstream) + # bitstream = iter(bitstream) -# print "-"*79 -# bitstream = list(bitstream) -# print_bitlist(bitstream) -# bitstream = iter(bitstream) -# print "-"*79 + block_type, block_length, codepoint_stream = self.get_block_info(bitstream) - codepoint_stream = bitstream2codepoints(bitstream) -# print "-"*79 -# print_codepoint_stream(codepoint_stream) -# print "-"*79 + codepoint_stream = list(codepoint_stream) + print "\n***** codepoint_stream length:", len(codepoint_stream) + pprint_codepoints(codepoint_stream) + print " -"*40 + codepoint_stream = iter(codepoint_stream) - block_type = next(codepoint_stream) -# print "raw block type:", repr(block_type), hex(block_type) - block_length = next(codepoint_stream) -# print "raw block length:", repr(block_length) + print "*** block length:", block_length - codepoint_stream = itertools.islice(codepoint_stream, block_length * 8) + try: + block_type_name = self.cfg.BLOCK_TYPE_DICT[block_type] + except KeyError: + print "ERROR: Block type %s unknown in BLOCK_TYPE_DICT!" % hex(block_type) + print "-"*79 + print "Debug bitlist:" + print_bitlist(bitstream) + print "-"*79 + sys.exit(-1) - return block_type, block_length, codepoint_stream + print "*** block type: 0x%x (%s)" % (block_type, block_type_name) + if block_type == self.cfg.EOF_BLOCK: + print "EOF-Block found" + break + if block_length == 0: + print "ERROR: block length == 0 ???" + print "-"*79 + print "Debug bitlist:" + print_bitlist(bitstream) + print "-"*79 + sys.exit(-1) -def bytes2codeline(raw_bytes): - """ - >>> data = (0x87,0x20,0x22,0x48,0x45,0x4c,0x4c,0x4f,0x20,0x57,0x4f,0x52,0x4c,0x44,0x21,0x22) - >>> bytes2codeline(data) - 'PRINT "HELLO WORLD!"' - """ - code_line = "" - func_token = False - for byte_no in raw_bytes: - if byte_no == 0xff: # Next byte is a function token - func_token = True - continue - elif func_token == True: - func_token = False - character = FUNCTION_TOKEN[byte_no] - elif byte_no in BASIC_TOKENS: - character = BASIC_TOKENS[byte_no] - else: - character = chr(byte_no) -# print byte_no, repr(character) - code_line += character - return code_line - - -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 "" % ( - repr(self.line_pointer), repr(self.line_no), repr(self.code) - ) + # block_codepoints = bitstream2codepoints(block_bits) + # block_codepoints = list(block_codepoints) + # print_codepoint_stream(block_codepoints) + # block_codepoints = iter(block_codepoints) -class FileContent(object): - """ - Content (all data blocks) of a cassette file. - """ - def __init__(self): - self.code_lines = [] - - def add_block_data(self, block_length, data): - """ - add a block of tokenized BASIC source code lines. - - >>> fc = FileContent() - - >>> block = [ - ... 0x1e,0x12,0x0,0xa,0x80,0x20,0x49,0x20,0xcb,0x20,0x31,0x20,0xbc,0x20,0x31,0x30,0x0, - ... 0x0,0x0] - >>> len(block) - 19 - >>> fc.add_block_data(19,iter(block)) - 19 Bytes parsed - >>> fc.print_code_lines() - 10 FOR I = 1 TO 10 - - >>> block = iter([ - ... 0x1e,0x29,0x0,0x14,0x87,0x20,0x49,0x3b,0x22,0x48,0x45,0x4c,0x4c,0x4f,0x20,0x57,0x4f,0x52,0x4c,0x44,0x21,0x22,0x0, - ... 0x0,0x0]) - >>> fc.add_block_data(999,block) - 25 Bytes parsed - ERROR: Block length value 999 is not equal to parsed bytes! - >>> fc.print_code_lines() - 10 FOR I = 1 TO 10 - 20 PRINT I;"HELLO WORLD!" - - >>> block = iter([ - ... 0x1e,0x31,0x0,0x1e,0x8b,0x20,0x49,0x0, - ... 0x0,0x0]) - >>> fc.add_block_data(10,block) - 10 Bytes parsed - >>> fc.print_code_lines() - 10 FOR I = 1 TO 10 - 20 PRINT I;"HELLO WORLD!" - 30 NEXT I - - - Test function tokens in code - - >>> fc = FileContent() - >>> data = iter([ - ... 0x1e,0x4a,0x0,0x1e,0x58,0xcb,0x58,0xc3,0x4c,0xc5,0xff,0x88,0x28,0x52,0x29,0x3a,0x59,0xcb,0x59,0xc3,0x4c,0xc5,0xff,0x89,0x28,0x52,0x29,0x0, - ... 0x0,0x0 - ... ]) - >>> fc.add_block_data(30, data) - 30 Bytes parsed - >>> fc.print_code_lines() - 30 X=X+L*SIN(R):Y=Y+L*COS(R) - - - Test high line numbers - - >>> fc = FileContent() - >>> 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 - ... ] - >>> len(data) - 99 - >>> fc.add_block_data(99, iter(data)) - 99 Bytes parsed - >>> fc.print_code_lines() - 1 PRINT "LINE NUMBER TEST" - 10 PRINT 10 - 100 PRINT 100 - 1000 PRINT 1000 - 10000 PRINT 10000 - 32768 PRINT 32768 - 63999 PRINT "END";63999 - """ - -# data = list(data) -# # print repr(data) -# print_as_hex_list(data) -# print_codepoint_stream(data) -# data = iter(data) -# sys.exit() - - byte_count = 0 - while True: - line_pointer = get_word(data) -# print "line_pointer:", repr(line_pointer) - byte_count += 2 - if not line_pointer: - # arrived [0x00, 0x00] -> end of block - break + self.cassette.add_block(block_type, block_length, codepoint_stream) + print "="*79 - try: - line_number = get_word(data) - except StopIteration: - print "No line number anymore" - break -# print "line_number:", repr(line_number) - byte_count += 2 -# data = list(data) -# print_as_hex_list(data) -# print_codepoint_stream(data) -# data = iter(data) + def sync_stream(self, bitstream): + sync_pattern = list(codepoints2bitstream(self.cfg.SYNC_BYTE_CODEPOINT)) + max_pos = (self.cfg.LEAD_BYTE_LEN + 2) * 8 - # get the code line: - # new iterator to get all characters until 0x00 arraived - code = iter(data.next, 0x00) + try: + sync_pos = find_iter_window(bitstream, sync_pattern, max_pos) + except MaxPosArraived, err: + print "\nError: Sync-Byte not found in the first %i Bytes! (%s)" % ( + self.cfg.LEAD_BYTE_LEN, err + ) + sys.exit(-1) + except PatternNotFound, err: + print "\nError: Sync-Byte doesn't exist in bitstream! (%s)" % err + sys.exit(-1) + else: + print "\nSync-Byte '%s' (%x) found at %i Bytes" % ( + list2str(sync_pattern), self.cfg.SYNC_BYTE_CODEPOINT, sync_pos + ) - code = list(code) # for len() - byte_count += len(code) + 1 # from 0x00 consumed in iter() -# print_as_hex_list(code) -# print_codepoint_stream(code) + def get_block_info(self, bitstream): +# print "-"*79 +# bitstream = list(bitstream) +# print_bitlist(bitstream, no_repr=True) +# bitstream = iter(bitstream) +# print "-"*79 - # convert to a plain ASCII string - code = bytes2codeline(code) + lead_in_pattern = list(codepoints2bitstream(self.cfg.LEAD_BYTE_CODEPOINT)) + max_pos = self.cfg.LEAD_BYTE_LEN * 8 - self.code_lines.append( - CodeLine(line_pointer, line_number, code) + # Searching for lead-in byte + try: + leader_pos = find_iter_window(bitstream, lead_in_pattern, max_pos) + except MaxPosArraived, err: + print "\nError: Leader-Byte '%s' (%s) not found in the first %i Bytes! (%s)" % ( + list2str(lead_in_pattern), hex(self.cfg.LEAD_BYTE_CODEPOINT), + self.cfg.LEAD_BYTE_LEN, err + ) + sys.exit(-1) + except PatternNotFound, err: + print "\nError: Leader-Byte '%s' (%s) doesn't exist in bitstream! (%s)" % ( + list2str(lead_in_pattern), hex(self.cfg.LEAD_BYTE_CODEPOINT), err + ) + sys.exit(-1) + else: + print "\nLeader-Byte '%s' (%s) found at %i Bytes" % ( + list2str(lead_in_pattern), hex(self.cfg.LEAD_BYTE_CODEPOINT), leader_pos ) - print "%i Bytes parsed" % byte_count - if block_length != byte_count: - print "ERROR: Block length value %i is not equal to parsed bytes!" % block_length - - def add_ascii_block(self, block_length, data): - """ - add a block of ASCII BASIC source code lines. - - >>> data = [ - ... 0xd, - ... 0x31,0x30,0x20,0x50,0x52,0x49,0x4e,0x54,0x20,0x22,0x54,0x45,0x53,0x54,0x22, - ... 0xd, - ... 0x32,0x30,0x20,0x50,0x52,0x49,0x4e,0x54,0x20,0x22,0x48,0x45,0x4c,0x4c,0x4f,0x20,0x57,0x4f,0x52,0x4c,0x44,0x21,0x22, - ... 0xd - ... ] - >>> len(data) - 41 - >>> fc = FileContent() - >>> fc.add_ascii_block(41, iter(data)) - 41 Bytes parsed - >>> fc.print_code_lines() - 10 PRINT "TEST" - 20 PRINT "HELLO WORLD!" - """ - data.next() # Skip first \r - byte_count = 1 # incl. first \r - while True: - code = iter(data.next, 0xd) # until \r - code = "".join([chr(c) for c in code]) - if not code: - break + # print "-"*79 + # print_bitlist(bitstream, no_repr=True) + # print "-"*79 + # sys.exit() - byte_count += len(code) + 1 # and \r consumed in iter() + self.sync_stream(bitstream) # Sync bitstream with SYNC_BYTE - try: - line_number, code = code.split(" ", 1) - except ValueError, err: - print "Error splitting linenumber in %s: %s" % (repr(code), err) - break + # print "-"*79 + # bitstream = list(bitstream) + # print_bitlist(bitstream) + # bitstream = iter(bitstream) + # print "-"*79 - line_number = int(line_number) + codepoint_stream = bitstream2codepoints(bitstream) + # print "-"*79 + # print_codepoint_stream(codepoint_stream) + # print "-"*79 - self.code_lines.append( - CodeLine(None, line_number, code) - ) + block_type = next(codepoint_stream) + # print "raw block type:", repr(block_type), hex(block_type) + block_length = next(codepoint_stream) + # print "raw block length:", repr(block_length) - print "%i Bytes parsed" % byte_count - if block_length != byte_count: - print "ERROR: Block length value %i is not equal to parsed bytes!" % block_length + codepoint_stream = itertools.islice(codepoint_stream, block_length) - def print_code_lines(self): - for code_line in self.code_lines: - print "%i %s" % (code_line.line_no, code_line.code) + return block_type, block_length, codepoint_stream -class CassetteFile(object): - """ - Representes a "file name block" and his "data block" - - 5.1 An 8 byte program name - 5.2 A file ID byte where: - 00=BASIC program - 01=Data file - 03=Binary file - 5.3 An ASCII flag where: - 00=Binary file - FF=ASCII file - 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. - 5.5 Two bytes for the default EXEC address - of a binary file. - 5.6 Two bytes for the default load address - of a binary file. - """ - def __init__(self, block_codepoints): - - raw_filename = list(itertools.islice(block_codepoints, 8)) - self.filename = codepoints2string(raw_filename) - print "Filename: %s (%s)" % (repr(self.filename), raw_filename) - - codepoints = list(block_codepoints) - -# print "file meta:" -# print_codepoint_stream(codepoints) - - self.file_type = codepoints[0] - print "file type:", repr(self.file_type) - if self.file_type == 0x00: - print "BASIC programm (0x00)" - elif self.file_type == 0x01: - print "Data file (0x01)" - raise NotImplemented("Data files are not supported, yet.") - elif self.file_type == 0xFF: - print "Binary file (0xFF)" - raise NotImplemented("Binary files are not supported, yet.") - else: - raise NotImplemented( - "Unknown file type %s is not supported, yet." % hex(self.file_type) - ) - ascii_flag = codepoints[1] - print "ASCII Flag is:", repr(ascii_flag) - if ascii_flag == 0x00: - print "tokenized BASIC" - self.is_tokenized = True - elif ascii_flag == 0xff: - print "ASCII BASIC" - self.is_tokenized = False - self.file_content = FileContent() - def add_block_data(self, block_length, codepoints): - if self.is_tokenized: - self.file_content.add_block_data(block_length, codepoints) - else: - self.file_content.add_ascii_block(block_length, codepoints) - print "*"*79 - self.file_content.print_code_lines() - print "*"*79 - - def __repr__(self): - return "" % (self.filename,) - - -class Cassette(object): - def __init__(self): - self.files = [] - self.current_file = None - - def add_block(self, block_type, block_length, block_codepoints): - if block_type == EOF_BLOCK: - return - elif block_type == FILENAME_BLOCK: - self.current_file = CassetteFile(block_codepoints) - print "Add file %s" % repr(self.current_file) - self.files.append(self.current_file) - elif block_type == DATA_BLOCK: - self.current_file.add_block_data(block_length, block_codepoints) - else: - raise TypeError("Block type %s unkown!" & hex(block_type)) def print_bit_list_stats(bit_list): @@ -533,8 +270,7 @@ def print_bit_list_stats(bit_list): # created by origin Dragon 32 machine - # 16Bit 44.1KHz mono -# FILENAME = "HelloWorld1 origin.wav" # no sync neede +# FILENAME = "HelloWorld1 origin.wav" # 16Bit 44.1KHz mono # Bit 1 min: 1764Hz avg: 2013.9Hz max: 2100Hz variation: 336Hz # Bit 0 min: 595Hz avg: 1090.2Hz max: 1336Hz Variation: 741Hz # 2710 Bits: 1217 positive bits and 1493 negative bits @@ -573,98 +309,52 @@ def print_bit_list_stats(bit_list): - # log_level = LOG_LEVEL_DICT[3] # args.verbosity - # log.setLevel(log_level) - - # logfilename = FILENAME + ".log" # args.logfile - # if logfilename: - # print "Log into '%s'" % logfilename - # handler = logging.FileHandler(logfilename, - # # mode='a', - # mode='w', - # encoding="utf8" - # ) - # handler.setFormatter(LOG_FORMATTER) - # log.addHandler(handler) +# log_level = LOG_LEVEL_DICT[3] # args.verbosity +# log.setLevel(log_level) +# +# logfilename = FILENAME + ".log" # args.logfile +# if logfilename: +# print "Log into '%s'" % logfilename +# handler = logging.FileHandler(logfilename, mode='w', encoding="utf8") +# handler.setFormatter(LOG_FORMATTER) +# log.addHandler(handler) +# +# # if args.stdout_log: +# handler = logging.StreamHandler() +# handler.setFormatter(LOG_FORMATTER) +# log.addHandler(handler) - # if args.stdout_log: - # handler = logging.StreamHandler() - # handler.setFormatter(LOG_FORMATTER) - # log.addHandler(handler) + d32cfg = Dragon32Config() + c = Cassette(d32cfg) - - - st = Wave2Bitstream(FILENAME, + # get bitstream from WAVE file: + st = Wave2Bitstream("test_files/%s" % FILENAME, bit_nul_hz=1200, # "0" is a single cycle at 1200 Hz bit_one_hz=2400, # "1" is a single cycle at 2400 Hz hz_variation=450, # How much Hz can signal scatter to match 1 or 0 bit ? + min_volume_ratio=5, # percent volume to ignore sample + mid_volume_ratio=15, # percent volume to trigger the sinus cycle ) bitstream = iter(st) bitstream.sync(32) bitstream = itertools.imap(lambda x: x[1], bitstream) # remove frame_no - # bit_list = array.array('B', bitstream) -# print "-"*79 -# bitstream = list(bitstream) -# sync_stream(bitstream) # Sync bitstream with SYNC_BYTE -# print_bitlist(bitstream) -# bitstream = iter(bitstream) -# print "-"*79 -# sys.exit() + # Create a bitstream from a existing .bas file: +# c.add_from_bas("test_files/HelloWorld1.bas") +# c.add_from_bas("test_files/Dragon Data Ltd - Examples from the Manual - 39~58 [run].bas") +# c.add_from_bas("test_files/LineNumberTest.bas") +# c.print_debug_info() +# bitstream = c.get_as_bitstream() + + # bitstream = list(bitstream) # print " ***** Bitstream length:", len(bitstream) +# print_bitlist(bitstream) # bitstream = iter(bitstream) - cassette = Cassette() - - while True: - print "_"*79 -# bitstream = list(bitstream) -# print " ***** Bitstream length:", len(bitstream) -# bitstream = iter(bitstream) - - block_type, block_length, codepoint_stream = get_block_info(bitstream) - -# codepoint_stream = list(codepoint_stream) -# print " ***** codepoint_stream length:", len(codepoint_stream) -# codepoint_stream = iter(codepoint_stream) - - print "*** block length:", block_length - - try: - block_type_name = BLOCK_TYPE_DICT[block_type] - except KeyError: - print "ERROR: Block type %s unknown in BLOCK_TYPE_DICT!" % hex(block_type) - print "-"*79 - print "Debug bitlist:" - print_bitlist(bitstream) - print "-"*79 - sys.exit(-1) - - - print "*** block type: 0x%x (%s)" % (block_type, block_type_name) - - if block_type == EOF_BLOCK: - print "EOF-Block found" - break - - if block_length == 0: - print "ERROR: block length == 0 ???" - print "-"*79 - print "Debug bitlist:" - print_bitlist(bitstream) - print "-"*79 - sys.exit(-1) - -# block_codepoints = bitstream2codepoints(block_bits) - -# block_codepoints = list(block_codepoints) -# print_codepoint_stream(block_codepoints) -# block_codepoints = iter(block_codepoints) - - cassette.add_block(block_type, block_length, codepoint_stream) - print "="*79 + bh = BitstreamHandler(d32cfg) + bh.feed(bitstream) diff --git a/PyDC/basic_tokens.py b/PyDC/basic_tokens.py index 030ca9b8..536cd43c 100644 --- a/PyDC/basic_tokens.py +++ b/PyDC/basic_tokens.py @@ -126,7 +126,43 @@ } + +def bytes2codeline(raw_bytes): + """ + >>> data = (0x87,0x20,0x22,0x48,0x45,0x4c,0x4c,0x4f,0x20,0x57,0x4f,0x52,0x4c,0x44,0x21,0x22) + >>> bytes2codeline(data) + 'PRINT "HELLO WORLD!"' + """ + code_line = "" + func_token = False + for byte_no in raw_bytes: + if byte_no == 0xff: # Next byte is a function token + func_token = True + continue + elif func_token == True: + func_token = False + try: + character = FUNCTION_TOKEN[byte_no] + except KeyError, err: + print "Error: BASIC function torken for '%s' not found!" % hex(byte_no) + character = chr(byte_no) + elif byte_no in BASIC_TOKENS: + try: + character = BASIC_TOKENS[byte_no] + except KeyError, err: + print "Error: BASIC torken for '%s' not found!" % hex(byte_no) + character = chr(byte_no) + else: + character = chr(byte_no) +# print byte_no, repr(character) + code_line += character + return code_line + + if __name__ == "__main__": + import doctest + print doctest.testmod() + def pprint_formated(d): for k, v in sorted(d.items()): print ' %s: "%s",' % (hex(k), v.strip()) diff --git a/PyDC/configs.py b/PyDC/configs.py new file mode 100644 index 00000000..5c1a4ffd --- /dev/null +++ b/PyDC/configs.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python2 +# coding: utf-8 + +""" + PyDC - configs + ============== + + :copyleft: 2013 by Jens Diemer + :license: GNU GPL v3 or above, see LICENSE for more details. +""" + +import inspect + + +class BaseConfig(object): + def print_debug_info(self): + from utils import byte2bit_string + + print "Config: '%s'" % self.__class__.__name__ + + for name, value in inspect.getmembers(self): # , inspect.isdatadescriptor): + if name.startswith("_"): + continue +# print name, type(value) + if not isinstance(value, (int, basestring, list, tuple, dict)): + continue + if isinstance(value, (int,)): + bit_string = byte2bit_string(value) + print "%20s = %-4s (in hex: %7s - binary: %s)" % ( + name, value, repr(hex(value)), bit_string + ) + else: + print "%20s = %s" % (name, value) + + +class Dragon32Config(BaseConfig): + """ + >>> d32cfg = Dragon32Config() + >>> d32cfg.print_debug_info() # doctest: +NORMALIZE_WHITESPACE + Config: 'Dragon32Config' + BIT_NUL_HZ = 1200 (in hex: '0x4b0' - binary: 00001101001) + BIT_ONE_HZ = 2400 (in hex: '0x960' - binary: 000001101001) + BLOCK_TYPE_DICT = {0: 'filename block (0x00)', 1: 'data block (0x01)', 255: 'end-of-file block (0xff)'} + DATA_BLOCK = 1 (in hex: '0x1' - binary: 10000000) + EOF_BLOCK = 255 (in hex: '0xff' - binary: 11111111) + FILENAME_BLOCK = 0 (in hex: '0x0' - binary: 00000000) + FILETYPE_DICT = {0: 'BASIC programm (0x00)', 1: 'Data file (0x01)', 255: 'Binary file (0xFF)'} + FTYPE_BASIC = 0 (in hex: '0x0' - binary: 00000000) + FTYPE_BIN = 255 (in hex: '0xff' - binary: 11111111) + FTYPE_DATA = 1 (in hex: '0x1' - binary: 10000000) + HZ_VARIATION = 450 (in hex: '0x1c2' - binary: 010000111) + LEAD_BYTE_CODEPOINT = 85 (in hex: '0x55' - binary: 10101010) + LEAD_BYTE_LEN = 255 (in hex: '0xff' - binary: 11111111) + SYNC_BYTE_CODEPOINT = 60 (in hex: '0x3c' - binary: 00111100) + + >>> ",".join([hex(c) for c in d32cfg.get_header_codepoint_stream()]) + ... # doctest: +ELLIPSIS + '0x55,0x55,0x55,...,0x55,0x55,0x55,0x3c' + """ + BIT_NUL_HZ = 1200 # "0" is a single cycle at 1200 Hz + BIT_ONE_HZ = 2400 # "1" is a single cycle at 2400 Hz + HZ_VARIATION = 450 # How much Hz can signal scatter to match 1 or 0 bit ? + + LEAD_BYTE_CODEPOINT = 0x55 # 10101010 + LEAD_BYTE_LEN = 255 + SYNC_BYTE_CODEPOINT = 0x3C # 00111100 + + # Block types: + FILENAME_BLOCK = 0x00 + DATA_BLOCK = 0x01 + EOF_BLOCK = 0xff + + BLOCK_TYPE_DICT = { + FILENAME_BLOCK: "filename block (0x00)", + DATA_BLOCK: "data block (0x01)", + EOF_BLOCK: "end-of-file block (0xff)", + } + + FTYPE_BASIC = 0x00 + FTYPE_DATA = 0x01 + FTYPE_BIN = 0xff + FILETYPE_DICT = { + FTYPE_BASIC:"BASIC programm (0x00)", + FTYPE_DATA:"Data file (0x01)", + FTYPE_BIN:"Binary file (0xFF)", + } + + BASIC_TOKENIZED = 0x00 + BASIC_ASCII = 0xff + BASIC_TYPE_DICT = { + BASIC_TOKENIZED:"tokenized BASIC (0x00)", + BASIC_ASCII:"ASCII BASIC (0xff)", + } + + BASIC_CODE_END = [0x00, 0x00] # Mark the end of the code + + + + +if __name__ == "__main__": + import doctest + print doctest.testmod( + verbose=False + # verbose=True + ) diff --git a/PyDC/test_files/Dragon Data Ltd - Examples from the Manual - 39~58 [run].bas b/PyDC/test_files/Dragon Data Ltd - Examples from the Manual - 39~58 [run].bas new file mode 100755 index 00000000..5e82b9b0 --- /dev/null +++ b/PyDC/test_files/Dragon Data Ltd - Examples from the Manual - 39~58 [run].bas @@ -0,0 +1,9 @@ +10 PMODE 4,1:SCREEN 1,1:PCLS 5:COLOR 0,5 +20 FOR I = 1 TO 1000 +30 X=X+L*SIN(R):Y=Y+L*COS(R) +40 IF X<-128 OR X>128 THEN 90 +50 IF Y<-96 OR Y>95 THEN 90 +60 LINE -(X+128,Y+96),PSET +70 R1=R1+60:R=R1/57.29578:L=L+0.5 +80 NEXT I +90 GOTO 90 \ No newline at end of file diff --git a/PyDC/test_files/Dragon Data Ltd - Examples from the Manual - 39~58 [run].wav b/PyDC/test_files/Dragon Data Ltd - Examples from the Manual - 39~58 [run].wav new file mode 100755 index 0000000000000000000000000000000000000000..fa3fe9d33bcad28ed023e418fd8ef0ddc5952882 GIT binary patch literal 5151847 zcmeF)zssH1*CzJwAR_|85DY2>0hg{+ihv=I850mG0!k_~3=9Dk0Tolb(H~%Noyvu) z=?v}-?hUSu{{XvFC)DJfc^T%{<=~6uz4y7#^TD{;EY@CY-ACuSpZocoeg2RC?O*+? zfBpad@BjF}{+IvrU;f|!&%gVh{>Oj(;~)RxpZ@z_|NTGyC?-~RaPyASXG_~nOh-n@MN^zox7&tANG_4ey;zyI;)Uw?o1{=bx{^siM&i?k!A0QMM z2}*+#AyrrzT89@RmY6AOiyI@?*g1Od;|D&1;bSO1isR!*K9c2QX+E0g0_!s zs_Wy*KCpr^g_yQ*|IDx?l3{GHh0)rD6oWS4&1}88$fx!t3PGE2XgA*8>z~BT1 zConjH!3hjbU~mG16BwMp-~z~BT1ConjH!3hjbU~mG16BwMp-~z~BT1ConjH z!3hjbU~mG16BwMp-~z~BT1ConjH!3hjbU~mG16BwMp-~z~BT1ConjH!3hjb zU~mG16BwMp-~z~BT1ConjH!3hjbU~mG16BwMp-~z~BT1ConjH!3hjbU~mG1 z6BwMp-~z~BT1ConjH!3hjbU~mG16BwMp-~z~BT1ConjH!3hjbU~mG16BwMp z-~z~BT1ConjH!3hjbU~mG16BwMp-~z~BT1ConjH!3hjbU~mG16BwMp-~
  • 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

    *{8BkWuMAE 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 C2HNBR literal 0 HcmV?d00001 diff --git a/PyDC/HelloWorld1 origin.wav b/PyDC/test_files/HelloWorld1 origin.wav similarity index 100% rename from PyDC/HelloWorld1 origin.wav rename to PyDC/test_files/HelloWorld1 origin.wav diff --git a/PyDC/HelloWorld1 xroar.wav b/PyDC/test_files/HelloWorld1 xroar.wav similarity index 100% rename from PyDC/HelloWorld1 xroar.wav rename to PyDC/test_files/HelloWorld1 xroar.wav diff --git a/PyDC/test_files/HelloWorld1.bas b/PyDC/test_files/HelloWorld1.bas new file mode 100755 index 00000000..f977b207 --- /dev/null +++ b/PyDC/test_files/HelloWorld1.bas @@ -0,0 +1,3 @@ +10 FOR I = 1 TO 10 +20 PRINT I;"HELLO WORLD!" +30 NEXT I \ No newline at end of file diff --git a/PyDC/LineNumber Test 01.wav b/PyDC/test_files/LineNumber Test 01.wav similarity index 100% rename from PyDC/LineNumber Test 01.wav rename to PyDC/test_files/LineNumber Test 01.wav diff --git a/PyDC/LineNumber Test 02.wav b/PyDC/test_files/LineNumber Test 02.wav similarity index 100% rename from PyDC/LineNumber Test 02.wav rename to PyDC/test_files/LineNumber Test 02.wav diff --git a/PyDC/test_files/LineNumberTest.bas b/PyDC/test_files/LineNumberTest.bas new file mode 100755 index 00000000..60755ae2 --- /dev/null +++ b/PyDC/test_files/LineNumberTest.bas @@ -0,0 +1,6 @@ +1 PRINT "LINE NUMBER TEST" +10 PRINT 10 +100 PRINT 100 +1000 PRINT 10000 +32768 PRINT 32768 +63999 PRINT "END";63999 \ No newline at end of file diff --git a/PyDC/utils.py b/PyDC/utils.py index 7b295419..f4e6ca72 100755 --- a/PyDC/utils.py +++ b/PyDC/utils.py @@ -12,6 +12,7 @@ import itertools import logging import types +import string LOG_FORMATTER = logging.Formatter("") # %(asctime)s %(message)s") @@ -377,6 +378,19 @@ def count_sign(values, min_value): def list2str(l): return "".join([str(c) for c in l]) +def string2codepoint(s): + """ + >>> codepoints = list(string2codepoint("HELLO")) + >>> codepoints + [72, 69, 76, 76, 79] + >>> ",".join([hex(c) for c in codepoints]) + '0x48,0x45,0x4c,0x4c,0x4f' + >>> print_codepoint_stream(codepoints) # doctest: +NORMALIZE_WHITESPACE + 5 | 0x48 'H' | 0x45 'E' | 0x4c 'L' | 0x4c 'L' | 0x4f 'O' | + """ + for char in s: + yield ord(char) + def bits2codepoint(bits): """ >>> c = bits2codepoint([0, 0, 0, 1, 0, 0, 1, 0]) @@ -434,6 +448,9 @@ def byte2bit_string(data): """ >>> byte2bit_string("H") '00010010' + + >>> byte2bit_string(0x55) + '10101010' """ if isinstance(data, basestring): assert len(data) == 1 @@ -443,6 +460,19 @@ def byte2bit_string(data): bits = bits[::-1] return bits +def codepoints2bitstream(codepoints): + """ + >>> list(codepoints2bitstream([0x48,0x45])) + [0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0] + >>> list(codepoints2bitstream(0x48)) + [0, 0, 0, 1, 0, 0, 1, 0] + """ + if isinstance(codepoints, int): + codepoints = [codepoints] + for codepoint in codepoints: + bit_string = byte2bit_string(codepoint) + for bit in bit_string: + yield int(bit) def byte_list2bit_list(data): """ @@ -527,6 +557,24 @@ def print_as_hex_list(codepoint_stream): """ print ",".join([hex(codepoint) for codepoint in codepoint_stream]) +def pprint_codepoints(codepoints): + """ + >>> pprint_codepoints([13, 70, 111, 111, 32, 66, 97, 114, 32, 33, 13]) + ['\r', 'Foo Bar !', '\r'] + """ + printable = string.printable.replace("\n", "").replace("\r", "") + line = [] + strings = "" + for codepoint in codepoints: + char = chr(codepoint) + if char in printable: + strings += char + else: + if strings != "": + line.append(strings) + strings = "" + line.append(char) + print line def print_block_bit_list(block_bit_list, display_block_count=8, no_repr=False): """ diff --git a/PyDC/wave2bitstream.py b/PyDC/wave2bitstream.py index 3ca9ba7e..b445cbcc 100644 --- a/PyDC/wave2bitstream.py +++ b/PyDC/wave2bitstream.py @@ -52,8 +52,8 @@ def __init__(self, wave_filename, bit_nul_hz, # sinus cycle frequency in Hz for one "0" bit bit_one_hz, # sinus cycle frequency in Hz for one "1" bit hz_variation, # How much Hz can signal scatter to match 1 or 0 bit ? - min_volume_ratio=5, # Ignore sample frames if lower volume - mid_volume_ratio=10, + min_volume_ratio=5, # percent volume to ignore sample + mid_volume_ratio=10, # percent volume to trigger the sinus cycle ): self.wave_filename = wave_filename @@ -79,10 +79,10 @@ def __init__(self, wave_filename, print "the max volume value is:", self.max_value self.min_volume = int(round(self.max_value * min_volume_ratio / 100)) - print "Ignore sample lower than %.f%% = %i" % (min_volume_ratio, self.min_volume) + print "Ignore sample lower than %.1f%% = %i" % (min_volume_ratio, self.min_volume) self.trigger_value = int(round(self.max_value * mid_volume_ratio / 100)) - print "Use trigger value: %.f%% = %i" % (mid_volume_ratio, self.trigger_value) + print "Use trigger value: %.1f%% = %i" % (mid_volume_ratio, self.trigger_value) # build min/max Hz values self.bit_nul_min_hz = bit_nul_hz - hz_variation @@ -195,6 +195,7 @@ def _print_status(frame_no, bit_count): bit_count = 0 + frame_no = False for frame_no, duration in iter_duration_generator: # if frame_no > 500: # sys.exit() @@ -234,6 +235,11 @@ def _print_status(frame_no, bit_count): next_status = time.time() + 1 _print_status(frame_no, bit_count) + if frame_no == False: + print "ERROR: No information from wave to generate the bits" + print "trigger volume to high?" + sys.exit(-1) + _print_status(frame_no, bit_count) print @@ -378,9 +384,9 @@ def iter_wave_values(self): # Ignore to lower amplitude skipped_values += 1 continue - elif skipped_values > 0: - log.debug(" *** Have %i samples skipped, because to lower amplitude." % skipped_values) - skipped_values = 0 +# elif skipped_values > 0: +# log.debug(" *** Have %i samples skipped, because to lower amplitude." % skipped_values) +# skipped_values = 0 msg = tlm.feed(value) if log.level >= logging.DEBUG: From 2f2593034f5536c95682f5c63786250c39cbf656 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Tue, 27 Aug 2013 09:00:11 +0200 Subject: [PATCH 036/151] bit-sync on every block start --- PyDC/PyDC.py | 75 +++++++++++++++++------------------------- PyDC/wave2bitstream.py | 32 +++++++++--------- 2 files changed, 45 insertions(+), 62 deletions(-) diff --git a/PyDC/PyDC.py b/PyDC/PyDC.py index 0295ddf5..bc5f29c2 100755 --- a/PyDC/PyDC.py +++ b/PyDC/PyDC.py @@ -148,11 +148,33 @@ def feed(self, bitstream): self.cassette.add_block(block_type, block_length, codepoint_stream) print "="*79 + def sync_bitstream(self, bitstream): + bitstream.sync(32) # Sync bitstream to wave sinus cycle - def sync_stream(self, bitstream): + # Searching for lead-in byte + lead_in_pattern = list(codepoints2bitstream(self.cfg.LEAD_BYTE_CODEPOINT)) + max_pos = self.cfg.LEAD_BYTE_LEN * 8 + try: + leader_pos = find_iter_window(bitstream, lead_in_pattern, max_pos) + except MaxPosArraived, err: + print "\nError: Leader-Byte '%s' (%s) not found in the first %i Bytes! (%s)" % ( + list2str(lead_in_pattern), hex(self.cfg.LEAD_BYTE_CODEPOINT), + self.cfg.LEAD_BYTE_LEN, err + ) + sys.exit(-1) + except PatternNotFound, err: + print "\nError: Leader-Byte '%s' (%s) doesn't exist in bitstream! (%s)" % ( + list2str(lead_in_pattern), hex(self.cfg.LEAD_BYTE_CODEPOINT), err + ) + sys.exit(-1) + else: + print "\nLeader-Byte '%s' (%s) found at %i Bytes" % ( + list2str(lead_in_pattern), hex(self.cfg.LEAD_BYTE_CODEPOINT), leader_pos + ) + + # Search for sync-byte sync_pattern = list(codepoints2bitstream(self.cfg.SYNC_BYTE_CODEPOINT)) max_pos = (self.cfg.LEAD_BYTE_LEN + 2) * 8 - try: sync_pos = find_iter_window(bitstream, sync_pattern, max_pos) except MaxPosArraived, err: @@ -176,34 +198,12 @@ def get_block_info(self, bitstream): # bitstream = iter(bitstream) # print "-"*79 - lead_in_pattern = list(codepoints2bitstream(self.cfg.LEAD_BYTE_CODEPOINT)) - max_pos = self.cfg.LEAD_BYTE_LEN * 8 - - # Searching for lead-in byte - try: - leader_pos = find_iter_window(bitstream, lead_in_pattern, max_pos) - except MaxPosArraived, err: - print "\nError: Leader-Byte '%s' (%s) not found in the first %i Bytes! (%s)" % ( - list2str(lead_in_pattern), hex(self.cfg.LEAD_BYTE_CODEPOINT), - self.cfg.LEAD_BYTE_LEN, err - ) - sys.exit(-1) - except PatternNotFound, err: - print "\nError: Leader-Byte '%s' (%s) doesn't exist in bitstream! (%s)" % ( - list2str(lead_in_pattern), hex(self.cfg.LEAD_BYTE_CODEPOINT), err - ) - sys.exit(-1) - else: - print "\nLeader-Byte '%s' (%s) found at %i Bytes" % ( - list2str(lead_in_pattern), hex(self.cfg.LEAD_BYTE_CODEPOINT), leader_pos - ) - # print "-"*79 # print_bitlist(bitstream, no_repr=True) # print "-"*79 # sys.exit() - self.sync_stream(bitstream) # Sync bitstream with SYNC_BYTE + self.sync_bitstream(bitstream) # Sync bitstream with SYNC_BYTE # print "-"*79 # bitstream = list(bitstream) @@ -276,20 +276,6 @@ def print_bit_list_stats(bit_list): # 2710 Bits: 1217 positive bits and 1493 negative bits - - """ - The origin BASIC code of the two WAV file is: - - 10 FOR I = 1 TO 10 - 20 PRINT I;"HELLO WORLD!" - 30 NEXT I - - The WAV files are here: - https://github.com/jedie/python-code-snippets/raw/master/CodeSnippets/Dragon%2032/HelloWorld1%20origin.wav - https://github.com/jedie/python-code-snippets/raw/master/CodeSnippets/Dragon%2032/HelloWorld1%20xroar.wav - """ - - # Test files from: # http://archive.worldofdragon.org/archive/index.php?dir=Tapes/Dragon/wav/ # FILENAME = "Quickbeam Software - Duplicas v3.0.wav" # binary! @@ -310,7 +296,8 @@ def print_bit_list_stats(bit_list): # log_level = LOG_LEVEL_DICT[3] # args.verbosity -# log.setLevel(log_level) +# log.setLevel(logging.DEBUG) + log.setLevel(logging.INFO) # # logfilename = FILENAME + ".log" # args.logfile # if logfilename: @@ -320,9 +307,9 @@ def print_bit_list_stats(bit_list): # log.addHandler(handler) # # # if args.stdout_log: -# handler = logging.StreamHandler() -# handler.setFormatter(LOG_FORMATTER) -# log.addHandler(handler) + handler = logging.StreamHandler() + handler.setFormatter(LOG_FORMATTER) + log.addHandler(handler) d32cfg = Dragon32Config() c = Cassette(d32cfg) @@ -336,8 +323,6 @@ def print_bit_list_stats(bit_list): mid_volume_ratio=15, # percent volume to trigger the sinus cycle ) bitstream = iter(st) - bitstream.sync(32) - bitstream = itertools.imap(lambda x: x[1], bitstream) # remove frame_no # Create a bitstream from a existing .bas file: diff --git a/PyDC/wave2bitstream.py b/PyDC/wave2bitstream.py index b445cbcc..0d0b4de1 100644 --- a/PyDC/wave2bitstream.py +++ b/PyDC/wave2bitstream.py @@ -99,6 +99,7 @@ def __init__(self, wave_filename, ) self.half_sinus = False # in trigger yield the full cycle + self.frame_no = None # create the generator chain: @@ -123,12 +124,12 @@ def sync(self, length): # go in wave stream to the first bit try: - first_bit_frame_no, first_bit = self.next() + self.next() except StopIteration: print "Error: no bits identified!" sys.exit(-1) - log.info("First bit is at: %s" % first_bit_frame_no) + log.info("First bit is at: %s" % self.frame_no) log.debug("enable half sinus scan") self.half_sinus = True @@ -149,11 +150,11 @@ def sync(self, length): log.debug("sync diff info: %i vs. %i" % (diff1, diff2)) if diff1 > diff2: - log.info("Sync one step.") + log.info("bit-sync one step.") self.iter_trigger_generator.next() log.debug("Synced.") else: - log.info("No sync needed.") + log.info("No bit-sync needed.") self.half_sinus = False log.debug("disable half sinus scan") @@ -206,7 +207,7 @@ def _print_status(frame_no, bit_count): "bit 1 at %s in %sSamples = %sHz" % (frame_no, duration, hz) ) bit_count += 1 - yield (frame_no, 1) + yield 1 one_hz_count += 1 if hz < one_hz_min: one_hz_min = hz @@ -218,7 +219,7 @@ def _print_status(frame_no, bit_count): "bit 0 at %s in %sSamples = %sHz" % (frame_no, duration, hz) ) bit_count += 1 - yield (frame_no, 0) + yield 0 nul_hz_count += 1 if hz < nul_hz_min: nul_hz_min = hz @@ -296,14 +297,14 @@ def iter_trigger(self, iter_wave_values): simmilar to a Schmitt trigger """ last_state = True - for frame_no, value in iter_wave_values: + for value in iter_wave_values: if last_state == False and value > self.trigger_value: log.debug( " ==== go into positive sinus cycle (%s > %s)===============" % ( value, self.trigger_value ) ) - yield frame_no + yield self.frame_no last_state = True elif last_state == True and value < -self.trigger_value: if self.half_sinus: @@ -312,7 +313,7 @@ def iter_trigger(self, iter_wave_values): value, self.trigger_value ) ) - yield frame_no + yield self.frame_no last_state = False # # def iter_trigger(self, iter_wave_values): @@ -357,7 +358,7 @@ def iter_wave_values(self): tlm = TextLevelMeter(self.max_value, 79) - frame_no = 0 + self.frame_no = 0 get_wave_block_func = functools.partial(self.wavefile.readframes, self.WAVE_READ_SIZE) skipped_values = 0 @@ -398,9 +399,9 @@ def iter_wave_values(self): "%s value: %i (%.1f%%)" % (msg, value, percent) ) - frame_no += 1 -# if frame_no > 100:sys.exit() - yield frame_no, value + self.frame_no += 1 +# if self.frame_no > 100:sys.exit() + yield value # def iter_wave_valuesOLD(self): # if self.samplewidth == 1: @@ -460,7 +461,7 @@ def iter_wave_values(self): # handler.setFormatter(LOG_FORMATTER) # log.addHandler(handler) - st = Wave2Bitstream(FILENAME, + st = Wave2Bitstream("test_files/%s" % FILENAME, bit_nul_hz=1200, # "0" is a single cycle at 1200 Hz bit_one_hz=2400, # "1" is a single cycle at 2400 Hz hz_variation=450, # How much Hz can signal scatter to match 1 or 0 bit ? @@ -471,9 +472,6 @@ def iter_wave_values(self): bitstream = iter(st) bitstream.sync(32) - bitstream = itertools.imap(lambda x: x[1], bitstream) -# bit_list = array.array('B', bitstream) - print "-"*79 print_bitlist(bitstream, no_repr=True) print "-"*79 From 9b009e7889b575fdd567172b2c44b89ff4ca39ef Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Tue, 27 Aug 2013 12:59:20 +0200 Subject: [PATCH 037/151] Implement a new trigger that works! See: http://www.python-forum.de/viewtopic.php?p=244702#p244702 (de) --- PyDC/PyDC.py | 14 ++--- PyDC/wave2bitstream.py | 131 ++++++++++++++++++++++++++--------------- 2 files changed, 89 insertions(+), 56 deletions(-) diff --git a/PyDC/PyDC.py b/PyDC/PyDC.py index bc5f29c2..581bfb56 100755 --- a/PyDC/PyDC.py +++ b/PyDC/PyDC.py @@ -262,7 +262,7 @@ def print_bit_list_stats(bit_list): # created by Xroar Emulator - FILENAME = "HelloWorld1 xroar.wav" # 8Bit 22050Hz +# FILENAME = "HelloWorld1 xroar.wav" # 8Bit 22050Hz # Bit 1 min: 1696Hz avg: 2058.3Hz max: 2205Hz variation: 509Hz # Bit 0 min: 595Hz avg: 1090.4Hz max: 1160Hz Variation: 565Hz # 4760 Bits: 2243 positive bits and 2517 negative bits @@ -270,7 +270,7 @@ def print_bit_list_stats(bit_list): # created by origin Dragon 32 machine -# FILENAME = "HelloWorld1 origin.wav" # 16Bit 44.1KHz mono + FILENAME = "HelloWorld1 origin.wav" # 16Bit 44.1KHz mono # Bit 1 min: 1764Hz avg: 2013.9Hz max: 2100Hz variation: 336Hz # Bit 0 min: 595Hz avg: 1090.2Hz max: 1336Hz Variation: 741Hz # 2710 Bits: 1217 positive bits and 1493 negative bits @@ -296,8 +296,8 @@ def print_bit_list_stats(bit_list): # log_level = LOG_LEVEL_DICT[3] # args.verbosity -# log.setLevel(logging.DEBUG) - log.setLevel(logging.INFO) + log.setLevel(logging.DEBUG) +# log.setLevel(logging.INFO) # # logfilename = FILENAME + ".log" # args.logfile # if logfilename: @@ -307,9 +307,9 @@ def print_bit_list_stats(bit_list): # log.addHandler(handler) # # # if args.stdout_log: - handler = logging.StreamHandler() - handler.setFormatter(LOG_FORMATTER) - log.addHandler(handler) +# handler = logging.StreamHandler() +# handler.setFormatter(LOG_FORMATTER) +# log.addHandler(handler) d32cfg = Dragon32Config() c = Cassette(d32cfg) diff --git a/PyDC/wave2bitstream.py b/PyDC/wave2bitstream.py index 0d0b4de1..dbed8670 100644 --- a/PyDC/wave2bitstream.py +++ b/PyDC/wave2bitstream.py @@ -22,7 +22,8 @@ # own modules from utils import average, diff_info, TextLevelMeter, iter_window, \ - human_duration, ProcessInfo, LOG_LEVEL_DICT, LOG_FORMATTER, print_bitlist + human_duration, ProcessInfo, LOG_LEVEL_DICT, LOG_FORMATTER, print_bitlist, \ + count_sign import struct import time @@ -257,30 +258,6 @@ def _print_status(frame_no, bit_count): one_hz_min, one_hz_max, one_hz_avg, one_hz_max - one_hz_min ) - - def auto_sync_duration(self, iter_duration): - """ - yield the duration of two frames in a row. - """ - for previous, current, next_value in itertools.islice(iter_window(iter_duration, window_size=3), 1, None, 2): - diff1 = abs(previous[1] - current[1]) - diff2 = abs(current[1] - next_value[1]) - - if diff1 < diff2: - result = (previous[0], previous[1] + current[1]) - log.debug("EVEN _%s_ + _%s_ | %s -> %s" % ( - repr(previous), repr(current), repr(next_value), - repr(result) - )) - else: - result = (current[0], current[1] + next_value[1]) - log.debug("ODD %s | _%s_ + _%s_ -> %s" % ( - repr(previous), repr(current), repr(next_value), - repr(result) - )) - - yield result - def iter_duration(self, iter_trigger): """ yield the duration of two frames in a row. @@ -291,31 +268,87 @@ def iter_duration(self, iter_trigger): yield (frame_no, duration) old_frame_no = frame_no - def iter_trigger(self, iter_wave_values): - """ - yield only the triggered frame numbers - simmilar to a Schmitt trigger - """ - last_state = True - for value in iter_wave_values: - if last_state == False and value > self.trigger_value: - log.debug( - " ==== go into positive sinus cycle (%s > %s)===============" % ( - value, self.trigger_value - ) - ) - yield self.frame_no - last_state = True - elif last_state == True and value < -self.trigger_value: + + def iter_trigger(self, iter_wave_values, +# end_count=4, mid_count=3 + end_count=3, mid_count=1 +# end_count=2, mid_count=1 + ): + window_size = (2 * end_count) + mid_count + + # sinus curve goes from negative into positive: + pos_null_transit = [(0, end_count), (end_count, 0)] + + # sinus curve goes from positive into negative: + neg_null_transit = [(end_count, 0), (0, end_count)] + + if mid_count > 3: + mid_index = int(round(mid_count / 2.0)) + else: + mid_index = 0 + + in_pos = False + for values in iter_window(iter_wave_values, window_size): +# if self.frame_no > 20: sys.exit() + +# print values + previous_values = values[:end_count] # e.g.: 123----- + mid_values = values[end_count:end_count + mid_count] # e.g.: ---45--- + next_values = values[-end_count:] # e.g.: -----678 + +# print previous_values, mid_values, next_values + + previous_values = [i[1] for i in previous_values] + next_values = [i[1] for i in next_values] + +# print previous_values, mid_values, next_values + + sign_info = [ + count_sign(previous_values, 0), + count_sign(next_values, 0) + ] +# print sign_info + if in_pos == False and sign_info == pos_null_transit: + log.debug("sinus curve goes from negative into positive") +# log.debug(" %s | %s | %s" % (previous_values, mid_values, next_values)) + yield mid_values[mid_index][0] + in_pos = True + elif in_pos == True and sign_info == neg_null_transit: if self.half_sinus: - log.debug( - " ---- go into netative -> yield half sinus (%s < -%s) ----------" % ( - value, self.trigger_value - ) - ) - yield self.frame_no - last_state = False + log.debug("sinus curve goes from positive into negative") +# log.debug(" %s | %s | %s" % (previous_values, mid_values, next_values)) + yield mid_values[mid_index][0] + in_pos = False + +# print + + +# def iter_trigger(self, iter_wave_values): +# """ +# yield only the triggered frame numbers +# simmilar to a Schmitt trigger +# """ +# last_state = True +# for value in iter_wave_values: +# if last_state == False and value > self.trigger_value: +# log.debug( +# " ==== go into positive sinus cycle (%s > %s)===============" % ( +# value, self.trigger_value +# ) +# ) +# yield self.frame_no +# last_state = True +# elif last_state == True and value < -self.trigger_value: +# if self.half_sinus: +# log.debug( +# " ---- go into netative -> yield half sinus (%s < -%s) ----------" % ( +# value, self.trigger_value +# ) +# ) +# yield self.frame_no +# last_state = False # + # def iter_trigger(self, iter_wave_values): # """ # yield only the triggered frame numbers @@ -401,7 +434,7 @@ def iter_wave_values(self): self.frame_no += 1 # if self.frame_no > 100:sys.exit() - yield value + yield self.frame_no, value # def iter_wave_valuesOLD(self): # if self.samplewidth == 1: From 515d55aabd5242f375f526c33c3022d3a70c3cb4 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Tue, 27 Aug 2013 13:57:44 +0200 Subject: [PATCH 038/151] code cleanup --- PyDC/PyDC.py | 19 ++--- PyDC/wave2bitstream.py | 172 ++++++++++------------------------------- 2 files changed, 52 insertions(+), 139 deletions(-) diff --git a/PyDC/PyDC.py b/PyDC/PyDC.py index 581bfb56..b94df091 100755 --- a/PyDC/PyDC.py +++ b/PyDC/PyDC.py @@ -270,7 +270,7 @@ def print_bit_list_stats(bit_list): # created by origin Dragon 32 machine - FILENAME = "HelloWorld1 origin.wav" # 16Bit 44.1KHz mono +# FILENAME = "HelloWorld1 origin.wav" # 16Bit 44.1KHz mono # Bit 1 min: 1764Hz avg: 2013.9Hz max: 2100Hz variation: 336Hz # Bit 0 min: 595Hz avg: 1090.2Hz max: 1336Hz Variation: 741Hz # 2710 Bits: 1217 positive bits and 1493 negative bits @@ -290,14 +290,14 @@ def print_bit_list_stats(bit_list): # FILENAME = "2_DBJ.WAV" # TODO # BASIC file with high line numbers: -# FILENAME = "LineNumber Test 01.wav" # tokenized BASIC - no sync + FILENAME = "LineNumber Test 01.wav" # tokenized BASIC - no sync # FILENAME = "LineNumber Test 02.wav" # ASCII BASIC - no sync # log_level = LOG_LEVEL_DICT[3] # args.verbosity - log.setLevel(logging.DEBUG) -# log.setLevel(logging.INFO) +# log.setLevel(logging.DEBUG) + log.setLevel(logging.INFO) # # logfilename = FILENAME + ".log" # args.logfile # if logfilename: @@ -307,9 +307,9 @@ def print_bit_list_stats(bit_list): # log.addHandler(handler) # # # if args.stdout_log: -# handler = logging.StreamHandler() -# handler.setFormatter(LOG_FORMATTER) -# log.addHandler(handler) + handler = logging.StreamHandler() + handler.setFormatter(LOG_FORMATTER) + log.addHandler(handler) d32cfg = Dragon32Config() c = Cassette(d32cfg) @@ -320,7 +320,8 @@ def print_bit_list_stats(bit_list): bit_one_hz=2400, # "1" is a single cycle at 2400 Hz hz_variation=450, # How much Hz can signal scatter to match 1 or 0 bit ? min_volume_ratio=5, # percent volume to ignore sample - mid_volume_ratio=15, # percent volume to trigger the sinus cycle + end_count=2, # Sample count that must be pos/neg at once + mid_count=1 # Sample count that can be around null ) bitstream = iter(st) @@ -333,7 +334,7 @@ def print_bit_list_stats(bit_list): # bitstream = c.get_as_bitstream() - +# bitstream.sync(32) # Sync bitstream to wave sinus cycle # bitstream = list(bitstream) # print " ***** Bitstream length:", len(bitstream) # print_bitlist(bitstream) diff --git a/PyDC/wave2bitstream.py b/PyDC/wave2bitstream.py index dbed8670..9afc5f39 100644 --- a/PyDC/wave2bitstream.py +++ b/PyDC/wave2bitstream.py @@ -12,6 +12,8 @@ import array import itertools import logging +import struct +import time try: import audioop @@ -23,9 +25,8 @@ # own modules from utils import average, diff_info, TextLevelMeter, iter_window, \ human_duration, ProcessInfo, LOG_LEVEL_DICT, LOG_FORMATTER, print_bitlist, \ - count_sign -import struct -import time + count_sign, iter_steps + log = logging.getLogger("PyDC") @@ -44,7 +45,7 @@ class Wave2Bitstream(object): # Maximum volume value in wave files: MAX_VALUES = { - 1: 255, # 8-bit wave file + 1: 255, # 8-bit wave file 2: 32768, # 16-bit wave file 4: 2147483647, # 32-bit wave file } @@ -54,10 +55,16 @@ def __init__(self, wave_filename, bit_one_hz, # sinus cycle frequency in Hz for one "1" bit hz_variation, # How much Hz can signal scatter to match 1 or 0 bit ? min_volume_ratio=5, # percent volume to ignore sample - mid_volume_ratio=10, # percent volume to trigger the sinus cycle + end_count=2, # Sample count that must be pos/neg at once + mid_count=1 # Sample count that can be around null ): self.wave_filename = wave_filename + assert end_count > 0 + assert mid_count > 0 + self.end_count = end_count + self.mid_count = mid_count + print "open wave file '%s'..." % wave_filename self.wavefile = wave.open(wave_filename, "rb") @@ -74,17 +81,12 @@ def __init__(self, wave_filename, self.samplewidth = self.wavefile.getsampwidth() # 1 for 8-bit, 2 for 16-bit, 4 for 32-bit samples print "samplewidth: %i (%sBit wave file)" % (self.samplewidth, self.samplewidth * 8) - - # build hysteresis min/max values: self.max_value = self.MAX_VALUES[self.samplewidth] print "the max volume value is:", self.max_value self.min_volume = int(round(self.max_value * min_volume_ratio / 100)) print "Ignore sample lower than %.1f%% = %i" % (min_volume_ratio, self.min_volume) - self.trigger_value = int(round(self.max_value * mid_volume_ratio / 100)) - print "Use trigger value: %.1f%% = %i" % (mid_volume_ratio, self.trigger_value) - # build min/max Hz values self.bit_nul_min_hz = bit_nul_hz - hz_variation self.bit_nul_max_hz = bit_nul_hz + hz_variation @@ -107,7 +109,7 @@ def __init__(self, wave_filename, # get frame numer + volume value from the WAVE file self.wave_values_generator = self.iter_wave_values() - # triggered frame numbers of a half sinus cycle + # trigger sinus cycle self.iter_trigger_generator = self.iter_trigger(self.wave_values_generator) # duration of a complete sinus cycle @@ -151,11 +153,11 @@ def sync(self, length): log.debug("sync diff info: %i vs. %i" % (diff1, diff2)) if diff1 > diff2: - log.info("bit-sync one step.") + log.info("\nbit-sync one step.") self.iter_trigger_generator.next() log.debug("Synced.") else: - log.info("No bit-sync needed.") + log.info("\nNo bit-sync needed.") self.half_sinus = False log.debug("disable half sinus scan") @@ -245,18 +247,16 @@ def _print_status(frame_no, bit_count): _print_status(frame_no, bit_count) print - print - print "%i Bits: %i positive bits and %i negative bits" % ( + log.info("%i Bits: %i positive bits and %i negative bits" % ( bit_count, one_hz_count, nul_hz_count - ) - print + )) if bit_count > 0: - print "Bit 0: %sHz - %sHz avg: %.1fHz variation: %sHz" % ( + log.info("Bit 0: %sHz - %sHz avg: %.1fHz variation: %sHz" % ( nul_hz_min, nul_hz_max, nul_hz_avg, nul_hz_max - nul_hz_min - ) - print "Bit 1: %sHz - %sHz avg: %.1fHz variation: %sHz" % ( + )) + log.info("Bit 1: %sHz - %sHz avg: %.1fHz variation: %sHz" % ( one_hz_min, one_hz_max, one_hz_avg, one_hz_max - one_hz_min - ) + )) def iter_duration(self, iter_trigger): """ @@ -269,21 +269,17 @@ def iter_duration(self, iter_trigger): old_frame_no = frame_no - def iter_trigger(self, iter_wave_values, -# end_count=4, mid_count=3 - end_count=3, mid_count=1 -# end_count=2, mid_count=1 - ): - window_size = (2 * end_count) + mid_count + def iter_trigger(self, iter_wave_values): + window_size = (2 * self.end_count) + self.mid_count # sinus curve goes from negative into positive: - pos_null_transit = [(0, end_count), (end_count, 0)] + pos_null_transit = [(0, self.end_count), (self.end_count, 0)] # sinus curve goes from positive into negative: - neg_null_transit = [(end_count, 0), (0, end_count)] + neg_null_transit = [(self.end_count, 0), (0, self.end_count)] - if mid_count > 3: - mid_index = int(round(mid_count / 2.0)) + if self.mid_count > 3: + mid_index = int(round(self.mid_count / 2.0)) else: mid_index = 0 @@ -292,22 +288,18 @@ def iter_trigger(self, iter_wave_values, # if self.frame_no > 20: sys.exit() # print values - previous_values = values[:end_count] # e.g.: 123----- - mid_values = values[end_count:end_count + mid_count] # e.g.: ---45--- - next_values = values[-end_count:] # e.g.: -----678 - -# print previous_values, mid_values, next_values + previous_values = values[:self.end_count] # e.g.: 123----- + mid_values = values[self.end_count:self.end_count + self.mid_count] # e.g.: ---45--- + next_values = values[-self.end_count:] # e.g.: -----678 + # (frame_no, value) tuple -> value list previous_values = [i[1] for i in previous_values] next_values = [i[1] for i in next_values] -# print previous_values, mid_values, next_values - sign_info = [ count_sign(previous_values, 0), count_sign(next_values, 0) ] -# print sign_info if in_pos == False and sign_info == pos_null_transit: log.debug("sinus curve goes from negative into positive") # log.debug(" %s | %s | %s" % (previous_values, mid_values, next_values)) @@ -320,61 +312,6 @@ def iter_trigger(self, iter_wave_values, yield mid_values[mid_index][0] in_pos = False -# print - - -# def iter_trigger(self, iter_wave_values): -# """ -# yield only the triggered frame numbers -# simmilar to a Schmitt trigger -# """ -# last_state = True -# for value in iter_wave_values: -# if last_state == False and value > self.trigger_value: -# log.debug( -# " ==== go into positive sinus cycle (%s > %s)===============" % ( -# value, self.trigger_value -# ) -# ) -# yield self.frame_no -# last_state = True -# elif last_state == True and value < -self.trigger_value: -# if self.half_sinus: -# log.debug( -# " ---- go into netative -> yield half sinus (%s < -%s) ----------" % ( -# value, self.trigger_value -# ) -# ) -# yield self.frame_no -# last_state = False -# - -# def iter_trigger(self, iter_wave_values): -# """ -# yield only the triggered frame numbers -# simmilar to a Schmitt trigger -# """ -# phase = 1 -# for frame_no, value in iter_wave_values: -# # print "frame no: %s, phase: %s, volume: %s" % (frame_no, phase, value) -# # if frame_no > 100: -# # sys.exit() -# if phase == 1 and value > self.hysteresis_max: -# log.debug(" ==== go into positive sinus cycle ===============") -# yield frame_no -# phase = 2 -# elif phase == 2 and value < self.hysteresis_min: -# log.debug(" ---- leave positive sinus cycle -----------------") -# phase = 3 -# elif phase == 3 and value < -self.hysteresis_max: -# log.debug(" ---- go into netative sinus cycle ---------------") -# if self.half_sinus: -# log.debug(" -**- yield half sinus -**-------------------------") -# yield frame_no -# phase = 4 -# elif phase == 4 and value > -self.hysteresis_min: -# log.debug(" ---- leave netative sinus cycle -----------------") -# phase = 1 def iter_wave_values(self): """ @@ -393,7 +330,7 @@ def iter_wave_values(self): self.frame_no = 0 get_wave_block_func = functools.partial(self.wavefile.readframes, self.WAVE_READ_SIZE) - skipped_values = 0 + skip_count = 0 manually_audioop_bias = self.samplewidth == 1 and audioop is None @@ -416,11 +353,8 @@ def iter_wave_values(self): if abs(value) < self.min_volume: # Ignore to lower amplitude - skipped_values += 1 + skip_count += 1 continue -# elif skipped_values > 0: -# log.debug(" *** Have %i samples skipped, because to lower amplitude." % skipped_values) -# skipped_values = 0 msg = tlm.feed(value) if log.level >= logging.DEBUG: @@ -434,34 +368,12 @@ def iter_wave_values(self): self.frame_no += 1 # if self.frame_no > 100:sys.exit() +# if self.frame_no > 4000:break yield self.frame_no, value -# def iter_wave_valuesOLD(self): -# if self.samplewidth == 1: -# struct_unpack_str = " 1000: break -# value = struct.unpack(struct_unpack_str, frame)[0] -# value = -value -# msg = tlm.feed(value) -# log.debug("%s value: %i" % (msg, value)) -# yield frame_no, value - + log.info("Skip %i samples that are lower than %i" % ( + skip_count, self.min_volume + )) if __name__ == "__main__": import doctest @@ -476,7 +388,8 @@ def iter_wave_values(self): # ~ FILENAME = "LineNumber Test 01.wav" # tokenized BASIC # log_level = LOG_LEVEL_DICT[3] # args.verbosity -# log.setLevel(log_level) +# log.setLevel(logging.DEBUG) + log.setLevel(logging.INFO) # # logfilename = FILENAME + ".log" # args.logfile # if logfilename: @@ -490,16 +403,15 @@ def iter_wave_values(self): # log.addHandler(handler) # if args.stdout_log: -# handler = logging.StreamHandler() -# handler.setFormatter(LOG_FORMATTER) -# log.addHandler(handler) + handler = logging.StreamHandler() + handler.setFormatter(LOG_FORMATTER) + log.addHandler(handler) st = Wave2Bitstream("test_files/%s" % FILENAME, bit_nul_hz=1200, # "0" is a single cycle at 1200 Hz bit_one_hz=2400, # "1" is a single cycle at 2400 Hz hz_variation=450, # How much Hz can signal scatter to match 1 or 0 bit ? # min_volume_ratio=0.01, # Ignore sample frames if lower volume -# mid_volume_ratio=0.2, hysteresis_ratio=0.1 ) bitstream = iter(st) From 94c398a17de20187503ea403531fc10e25048e2f Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Tue, 27 Aug 2013 14:14:13 +0200 Subject: [PATCH 039/151] add a 'average sample' function, but turn it off is the best idea ;) --- PyDC/PyDC.py | 1 + PyDC/wave2bitstream.py | 144 ++++++++++++----------------------------- 2 files changed, 44 insertions(+), 101 deletions(-) diff --git a/PyDC/PyDC.py b/PyDC/PyDC.py index b94df091..b3029446 100755 --- a/PyDC/PyDC.py +++ b/PyDC/PyDC.py @@ -320,6 +320,7 @@ def print_bit_list_stats(bit_list): bit_one_hz=2400, # "1" is a single cycle at 2400 Hz hz_variation=450, # How much Hz can signal scatter to match 1 or 0 bit ? min_volume_ratio=5, # percent volume to ignore sample + avg_count=0, # How many samples should be merged into a average value? end_count=2, # Sample count that must be pos/neg at once mid_count=1 # Sample count that can be around null ) diff --git a/PyDC/wave2bitstream.py b/PyDC/wave2bitstream.py index 9afc5f39..671a6055 100644 --- a/PyDC/wave2bitstream.py +++ b/PyDC/wave2bitstream.py @@ -55,6 +55,7 @@ def __init__(self, wave_filename, bit_one_hz, # sinus cycle frequency in Hz for one "1" bit hz_variation, # How much Hz can signal scatter to match 1 or 0 bit ? min_volume_ratio=5, # percent volume to ignore sample + avg_count=0, # How many samples should be merged into a average value? end_count=2, # Sample count that must be pos/neg at once mid_count=1 # Sample count that can be around null ): @@ -64,6 +65,7 @@ def __init__(self, wave_filename, assert mid_count > 0 self.end_count = end_count self.mid_count = mid_count + self.avg_count = avg_count print "open wave file '%s'..." % wave_filename self.wavefile = wave.open(wave_filename, "rb") @@ -109,8 +111,15 @@ def __init__(self, wave_filename, # get frame numer + volume value from the WAVE file self.wave_values_generator = self.iter_wave_values() - # trigger sinus cycle - self.iter_trigger_generator = self.iter_trigger(self.wave_values_generator) + if avg_count > 1: + # merge samples to a average sample + log.debug("Merge %s audio sample to one average sample" % self.avg_count) + self.avg_wave_values_generator = self.iter_avg_wave_values(self.wave_values_generator) + # trigger sinus cycle + self.iter_trigger_generator = self.iter_trigger(self.avg_wave_values_generator) + else: + # trigger sinus cycle + self.iter_trigger_generator = self.iter_trigger(self.wave_values_generator) # duration of a complete sinus cycle self.iter_duration_generator = self.iter_duration(self.iter_trigger_generator) @@ -182,10 +191,8 @@ def iter_bitstream(self, iter_duration_generator): def _print_status(frame_no, bit_count): ms = float(frame_no) / self.framerate rest, eta, rate = process_info.update(frame_no) - sys.stdout.write( - "\r%i frames (wav pos:%s) get %iBits - eta: %s (rate: %iFrames/sec) " % ( - frame_no, human_duration(ms), bit_count, eta, rate - ) + print "%i frames (wav pos:%s) get %iBits - eta: %s (rate: %iFrames/sec)" % ( + frame_no, human_duration(ms), bit_count, eta, rate ) one_hz_count = 0 @@ -247,7 +254,7 @@ def _print_status(frame_no, bit_count): _print_status(frame_no, bit_count) print - log.info("%i Bits: %i positive bits and %i negative bits" % ( + log.info("\n%i Bits: %i positive bits and %i negative bits" % ( bit_count, one_hz_count, nul_hz_count )) if bit_count > 0: @@ -312,6 +319,31 @@ def iter_trigger(self, iter_wave_values): yield mid_values[mid_index][0] in_pos = False +# print + + def iter_avg_wave_values(self, wave_values_generator): + if self.avg_count == 3: + mid_index = 1 + elif self.avg_count > 3: + mid_index = int(round(self.avg_count / 2.0)) + else: + mid_index = 0 + assert mid_index < self.avg_count + + for value_tuples in iter_steps(wave_values_generator, self.avg_count): + # (frame_no, value) tuple -> value list + values = sum([i[1] for i in value_tuples]) / self.avg_count + try: + frame_no = value_tuples[mid_index][0] + except IndexError, err: + print "\nError getting %i from; %s: %s" % (mid_index, repr(value_tuples), err) + frame_no = value_tuples[0][0] + result = (frame_no, values) +# if log.level >= logging.DEBUG: +# log.debug("average %s samples to: %s" % (repr(value_tuples), repr(result))) +# print "average %s samples to: %s" % (repr(value_tuples), repr(result)) + yield result + def iter_wave_values(self): """ @@ -411,7 +443,10 @@ def iter_wave_values(self): bit_nul_hz=1200, # "0" is a single cycle at 1200 Hz bit_one_hz=2400, # "1" is a single cycle at 2400 Hz hz_variation=450, # How much Hz can signal scatter to match 1 or 0 bit ? -# min_volume_ratio=0.01, # Ignore sample frames if lower volume + +# avg_count=2, # How many samples should be merged into a average value? +# end_count=2, # Sample count that must be pos/neg at once +# mid_count=1 # Sample count that can be around null ) bitstream = iter(st) @@ -423,96 +458,3 @@ def iter_wave_values(self): print "-- END --" - -""" -open wave file 'HelloWorld1 xroar.wav'... -Framerate: 22050 -Number of audio frames: 75025 -channels: 1 -samplewidth: 1 (8Bit wave file) -Use trigger value: 51.0 -First bit is at: 15 -enable half sinus scan -sync diff info: 32 vs. 5 -No sync needed. -disable half sinus scan - 0 - 10101100 10110100 11010110 01011010 01101010 01101100 10101100 10110100 - 8 - 11010110 01011010 01101010 01101100 10101100 10110100 11010110 01011010 - 16 - 01101010 01101100 10101100 10110100 11010110 01011010 01101010 01101100 - 24 - 10101100 10110100 11010110 01011010 01101010 01101100 10101100 10110100 - 32 - 11010110 01011010 01101010 01101100 10101100 10110100 11010110 01011010 - 40 - 01101010 01101100 10101100 10110100 11010110 01011010 01101010 01101100 - 48 - 10101100 10110100 11010110 01011010 01101010 01101100 10101100 10110100 - 56 - 11010110 11011010 01101010 01101100 10101100 10110100 11010110 11011010 - 64 - 01101010 01101100 10101100 10110100 11010110 11011010 01101010 01101100 - 72 - 10101100 10110100 11010110 11011010 01101010 01101100 10101100 10110100 - 80 - 11010110 11011010 01101010 01101100 10101100 10110100 11010110 11011010 - 88 - 01101010 01101100 10101100 10110100 11010110 11011010 01101010 01101100 - 96 - 10101100 10110100 11010110 11011010 01101010 01101100 10101100 10110100 - 104 - 11010110 11011010 01101010 01101100 10101100 10110100 11010110 11011010 - 112 - 01101010 01101100 10101100 10110100 11010110 11011010 01011010 01101100 - 120 - 10101100 10110100 11010110 11011010 01011010 01101100 10101100 10110100 - 128 - 11010110 11011010 01011010 01101100 10101100 10110100 11010110 11011010 - 136 - 01011010 01101100 10101100 10110100 11010110 11011010 01011010 01101100 - 144 - 10101100 10110100 11010110 11011010 01011010 01101100 10101100 10110100 - 152 - 11010110 11011010 01011010 01101100 10101100 10110100 11010110 11011010 - 160 - 01011010 01101100 10101100 10110100 11010110 11011010 01011010 01101100 - 168 - 10101100 10110100 11010110 11011010 01011010 01101100 10101100 10110100 - 176 - 11010110 11011010 01011010 01101010 10101100 10110100 11010110 11011010 - 184 - 01011010 01101010 10101100 10110100 11010110 11011010 01011010 01101010 - 192 - 10101100 10110100 11010110 11011010 01011010 01101010 10101100 10110100 - 200 - 11010110 11011010 01011010 01101010 10101100 10110100 11010110 11011010 - 208 - 01011010 01101010 10101100 10110100 11010110 11011010 01011010 01101010 - 216 - 10101100 10110100 11010110 11011010 01011010 01101010 10101100 10110100 - 224 - 11010110 11011010 01011010 01101010 10101100 10110100 11010110 11011010 - 232 - 01011010 01101010 10101100 10110100 11010110 11011010 01011010 01101010 - 240 - 10101100 10110100 11010110 11011010 01011010 01101010 10101100 10110100 - 248 - 11010110 11011010 01011010 01101010 10101100 10110100 11010110 01101100 - 256 - 01111000 00000000 11100000 00000100 00000100 00000100 00001100 00001000 - 264 - 00001000 00000100 00000100 00000000 00000000 00000000 00000000 00000000 - 272 - 00000000 00000000 11110000 11010110 10110100 10110110 11010110 01011010 - 280 - 01101010 10101100 10110100 10110110 11010110 01011010 01101010 10101100 - 288 - 10110100 10110110 11010110 01011010 01101010 10101100 10110100 10110110 - 296 - 11010110 01011010 01101010 10101100 10110100 10110110 11010110 01011010 - 304 - 01101010 10101100 10110100 10110110 11010110 01011010 01101010 10101100 - 312 - 10110100 10110110 11010110 01011010 01101010 10101100 10110100 10110110 - 320 - 11010110 01011010 01101010 10101100 10110100 10110110 11010110 01011010 - 328 - 01101010 10101100 10110100 10110110 11010110 01011010 01101010 10101100 - 336 - 10110100 10110110 11010110 01011010 01101010 10101100 10110100 10110110 - 344 - 11010110 01011010 01101010 10101100 10110100 10110110 11010110 01011010 - 352 - 01101010 10101100 10110100 10110110 11010110 01011010 01101010 10101100 - 360 - 10110100 10110110 11010110 01011010 01101010 10101100 10110100 10110110 - 368 - 11010110 01011010 01101010 10101100 10110100 10110110 11010110 01011010 - 376 - 01101010 01101100 10110100 10110110 11010110 01011010 01101010 01101100 - 384 - 10110100 10110110 11010110 01011010 01101010 01101100 10110100 10110110 - 392 - 11010110 01011010 01101010 01101100 10110100 10110110 11010110 01011010 - 400 - 01101010 01101100 10110100 10110110 11010110 01011010 01101010 01101100 - 408 - 10110100 10110110 11010110 01011010 01101010 01101100 10110100 10110110 - 416 - 11010110 01011010 01101010 01101100 10110100 10110110 11010110 01011010 - 424 - 01101010 01101100 10110100 10110110 11010110 01011010 01101010 01101100 - 432 - 10110100 10110110 11010110 01011010 01101010 01101100 10101100 10110110 - 440 - 11010110 01011010 01101010 01101100 10101100 10110110 11010110 01011010 - 448 - 01101010 01101100 10101100 10110110 11010110 01011010 01101010 01101100 - 456 - 10101100 10110110 11010110 01011010 01101010 01101100 10101100 10110110 - 464 - 11010110 01011010 01101010 01101100 10101100 10110110 11010110 01011010 - 472 - 01101010 01101100 10101100 10110110 11010110 01011010 01101010 01101100 - 480 - 10101100 10110110 11010110 01011010 01101010 01101100 10101100 10110110 - 488 - 11010110 01011010 01101010 01101100 10101100 10110110 11010110 01011010 - 496 - 01101010 01101100 10101100 10110100 11010110 01011010 01101010 01101100 - 504 - 10101100 10110100 11010110 01011010 01101010 01101100 10101100 10110100 - 512 - 11010110 01011010 01101010 01101100 10101100 10110100 11010110 01011010 - 520 - 01101010 01101100 10101100 10110100 11010110 01011010 01101010 01101100 - 528 - 10101100 10110100 11010110 01011010 01011010 01111100 00000000 10011100 - 536 - 01111000 11001000 00000000 10110000 00000010 00001000 00100110 00001100 - 544 - 11010110 00001000 10011100 00000100 00111100 00000100 10001000 00011000 - 552 - 00000000 01111000 10011000 00000000 01101000 11000010 00001100 10010010 - 560 - 10111100 10001000 00100100 01100010 00100100 01110010 11110010 00000100 - 568 - 11011010 11110010 01010100 01110010 00100100 00001000 10001000 00000000 - 576 - 01111000 10001000 00000000 01111000 11100010 00001000 00100110 00000000 - 584 - 00000000 00000000 11110100 11010110 11011010 00111100 11111110 00000000 - -4752 Bits: 2401 positive bits and 2351 negative bits - -Bit 1: 1470-2205Hz avg: 1487.6Hz variation: 735Hz -Bit 0: 689-1378Hz avg: 1332.3Hz variation: 689Hz -""" From 1a2426eb76b4eb2575b49a67a8c89d14c28e036f Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Tue, 27 Aug 2013 17:16:23 +0200 Subject: [PATCH 040/151] Add a command line interface --- PyDC/{ => PyDC}/CassetteObjects.py | 54 +++++------ PyDC/{ => PyDC}/PyDC.py | 69 +++++++------ PyDC/PyDC/__init__.py | 25 +++++ PyDC/PyDC/base_cli.py | 110 +++++++++++++++++++++ PyDC/{ => PyDC}/basic_tokens.py | 0 PyDC/{ => PyDC}/configs.py | 0 PyDC/{ => PyDC}/utils.py | 1 + PyDC/{ => PyDC}/wave2bitstream.py | 6 +- PyDC/PyDC_cli.py | 151 +++++++++++++++++++++++++++++ PyDC/README.creole | 45 +++++++++ 10 files changed, 402 insertions(+), 59 deletions(-) rename PyDC/{ => PyDC}/CassetteObjects.py (93%) rename PyDC/{ => PyDC}/PyDC.py (83%) create mode 100644 PyDC/PyDC/__init__.py create mode 100644 PyDC/PyDC/base_cli.py rename PyDC/{ => PyDC}/basic_tokens.py (100%) rename PyDC/{ => PyDC}/configs.py (100%) rename PyDC/{ => PyDC}/utils.py (99%) rename PyDC/{ => PyDC}/wave2bitstream.py (98%) create mode 100755 PyDC/PyDC_cli.py diff --git a/PyDC/CassetteObjects.py b/PyDC/PyDC/CassetteObjects.py similarity index 93% rename from PyDC/CassetteObjects.py rename to PyDC/PyDC/CassetteObjects.py index 7ccb0711..23347134 100644 --- a/PyDC/CassetteObjects.py +++ b/PyDC/PyDC/CassetteObjects.py @@ -33,8 +33,11 @@ def __init__(self, line_pointer, line_no, code): self.line_no = line_no self.code = code + def get_ascii_codeline(self): + return "%i %s" % (self.line_no, self.code) + def get_as_codepoints(self): - return string2codepoint("%i %s" % (self.line_no, self.code)) + return string2codepoint(self.get_ascii_codeline()) def __repr__(self): return "" % ( @@ -242,15 +245,19 @@ def add_ascii_block(self, block_length, data): def get_as_codepoints(self): result = [] delim = list(string2codepoint("\r"))[0] - for codepoints in self.code_lines: + for code_line in self.code_lines: result.append(delim) - result += list(codepoints.get_as_codepoints()) + result += list(code_line.get_as_codepoints()) result.append(delim) result += self.cfg.BASIC_CODE_END log.debug("code: %s" % repr(result)) return result + def get_ascii_codeline(self): + for code_line in self.code_lines: + yield code_line.get_ascii_codeline() + def print_code_lines(self): for code_line in self.code_lines: print "%i %s" % (code_line.line_no, code_line.code) @@ -263,27 +270,6 @@ def print_debug_info(self): class CassetteFile(object): - """ - Representes a "file name block" and his "data block" - - 5.1 An 8 byte program name - 5.2 A file ID byte where: - 00=BASIC program - 01=Data file - 03=Binary file - 5.3 An ASCII flag where: - 00=Binary file - FF=ASCII file - 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. - 5.5 Two bytes for the default EXEC address - of a binary file. - 5.6 Two bytes for the default load address - of a binary file. - """ def __init__(self, cfg): self.cfg = cfg self.is_tokenized = False @@ -291,9 +277,10 @@ def __init__(self, cfg): def create_from_bas(self, filename, file_content): filename2 = os.path.split(filename)[1] filename2 = filename2.upper() + filename2 = filename2.rstrip() filename2 = filename2.replace(" ", "_") # TODO: remove non ASCII! - filename2 = filename2[:8].ljust(8, " ") + filename2 = filename2[:8] log.debug("filename '%s' from: %s" % (filename2, filename)) @@ -311,7 +298,7 @@ def create_from_wave(self, block_codepoints): raw_filename = list(itertools.islice(block_codepoints, 8)) - self.filename = codepoints2string(raw_filename) + self.filename = codepoints2string(raw_filename).rstrip() print "\nFilename: %s" % repr(self.filename) codepoints = list(block_codepoints) @@ -357,7 +344,7 @@ def add_block_data(self, block_length, codepoints): def get_filename_block_as_codepoints(self): codepoints = [] - codepoints += list(string2codepoint(self.filename)) + codepoints += list(string2codepoint(self.filename.ljust(8, " "))) codepoints.append(self.cfg.FTYPE_BASIC) codepoints.append(self.cfg.BASIC_ASCII) # ASCII BASIC return codepoints @@ -497,6 +484,19 @@ def get_as_bitstream(self): for bit in codepoints2bitstream(codepoint): yield bit + def save_bas(self, destination_file): + dest_filename, dest_ext = os.path.splitext(destination_file) + for file_obj in self.files: + + bas_filename = file_obj.filename # Filename from CSAVE argument + + out_filename = "%s_%s.bas" % (dest_filename, bas_filename) + log.info("Create %s..." % repr(out_filename)) + with open(out_filename, "w") as f: + for line in file_obj.file_content.get_ascii_codeline(): + f.write("%s\n" % line) + print "\nFile %s saved." % repr(out_filename) + def pprint_codepoint_stream(self): log_level = LOG_LEVEL_DICT[3] log.setLevel(log_level) diff --git a/PyDC/PyDC.py b/PyDC/PyDC/PyDC.py similarity index 83% rename from PyDC/PyDC.py rename to PyDC/PyDC/PyDC.py index b3029446..e061cf4b 100755 --- a/PyDC/PyDC.py +++ b/PyDC/PyDC/PyDC.py @@ -2,15 +2,12 @@ # coding: utf-8 """ - Convert dragon 32 Cassetts WAV files into plain text. - ===================================================== - - Currently ony supported: - * BASIC programs in tokenised form + Convert dragon 32 Cassetts WAV files + ==================================== TODO: - - check BASIC programs in ASCII form: CSAVE "NAME",A - - detect even_odd startpoint! + - test block checksum, see: + http://archive.worldofdragon.org/phpBB3/viewtopic.php?f=8&t=4231&p=8905#p8905 - add cli - write .BAS file @@ -19,7 +16,6 @@ """ -import sys import itertools import logging from configs import Dragon32Config @@ -29,11 +25,9 @@ # own modules -from basic_tokens import BASIC_TOKENS, FUNCTION_TOKEN from utils import find_iter_window, iter_steps, MaxPosArraived, \ - print_bitlist, bits2codepoint, list2str, bitstream2codepoints, get_word, \ - print_codepoint_stream, codepoints2string, print_as_hex_list, \ - PatternNotFound, LOG_LEVEL_DICT, LOG_FORMATTER, codepoints2bitstream, \ + print_bitlist, bits2codepoint, list2str, bitstream2codepoints, \ + PatternNotFound, LOG_FORMATTER, codepoints2bitstream, \ pprint_codepoints from wave2bitstream import Wave2Bitstream @@ -44,6 +38,10 @@ +class SyncByteNotFoundError(Exception): + pass + + def pop_bytes_from_bit_list(bit_list, count): """ >>> bit_str = ( @@ -104,7 +102,11 @@ def feed(self, bitstream): # print " ***** Bitstream length:", len(bitstream) # bitstream = iter(bitstream) - block_type, block_length, codepoint_stream = self.get_block_info(bitstream) + try: + block_type, block_length, codepoint_stream = self.get_block_info(bitstream) + except SyncByteNotFoundError, err: + log.error(err) + break codepoint_stream = list(codepoint_stream) print "\n***** codepoint_stream length:", len(codepoint_stream) @@ -122,7 +124,7 @@ def feed(self, bitstream): print "Debug bitlist:" print_bitlist(bitstream) print "-"*79 - sys.exit(-1) + break print "*** block type: 0x%x (%s)" % (block_type, block_type_name) @@ -137,7 +139,7 @@ def feed(self, bitstream): print "Debug bitlist:" print_bitlist(bitstream) print "-"*79 - sys.exit(-1) + break # block_codepoints = bitstream2codepoints(block_bits) @@ -157,20 +159,18 @@ def sync_bitstream(self, bitstream): try: leader_pos = find_iter_window(bitstream, lead_in_pattern, max_pos) except MaxPosArraived, err: - print "\nError: Leader-Byte '%s' (%s) not found in the first %i Bytes! (%s)" % ( + log.error("Error: Leader-Byte '%s' (%s) not found in the first %i Bytes! (%s)" % ( list2str(lead_in_pattern), hex(self.cfg.LEAD_BYTE_CODEPOINT), self.cfg.LEAD_BYTE_LEN, err - ) - sys.exit(-1) + )) except PatternNotFound, err: - print "\nError: Leader-Byte '%s' (%s) doesn't exist in bitstream! (%s)" % ( + log.error("Error: Leader-Byte '%s' (%s) doesn't exist in bitstream! (%s)" % ( list2str(lead_in_pattern), hex(self.cfg.LEAD_BYTE_CODEPOINT), err - ) - sys.exit(-1) + )) else: - print "\nLeader-Byte '%s' (%s) found at %i Bytes" % ( + log.info("Leader-Byte '%s' (%s) found at %i Bytes" % ( list2str(lead_in_pattern), hex(self.cfg.LEAD_BYTE_CODEPOINT), leader_pos - ) + )) # Search for sync-byte sync_pattern = list(codepoints2bitstream(self.cfg.SYNC_BYTE_CODEPOINT)) @@ -178,17 +178,24 @@ def sync_bitstream(self, bitstream): try: sync_pos = find_iter_window(bitstream, sync_pattern, max_pos) except MaxPosArraived, err: - print "\nError: Sync-Byte not found in the first %i Bytes! (%s)" % ( - self.cfg.LEAD_BYTE_LEN, err + raise SyncByteNotFoundError( + "Error: Sync-Byte '%s' (%s) not found in the first %i Bytes! (%s)" % ( + list2str(sync_pattern), hex(self.cfg.SYNC_BYTE_CODEPOINT), + self.cfg.LEAD_BYTE_LEN, err + ) ) - sys.exit(-1) except PatternNotFound, err: - print "\nError: Sync-Byte doesn't exist in bitstream! (%s)" % err - sys.exit(-1) - else: - print "\nSync-Byte '%s' (%x) found at %i Bytes" % ( - list2str(sync_pattern), self.cfg.SYNC_BYTE_CODEPOINT, sync_pos + raise SyncByteNotFoundError( + "Error: Sync-Byte '%s' (%s) doesn't exist in bitstream! (%s)" % ( + list2str(sync_pattern), hex(self.cfg.SYNC_BYTE_CODEPOINT), + err + ) ) + else: + log.info("Sync-Byte '%s' (%s) found at %i Bytes" % ( + list2str(sync_pattern), hex(self.cfg.SYNC_BYTE_CODEPOINT), + sync_pos + )) def get_block_info(self, bitstream): diff --git a/PyDC/PyDC/__init__.py b/PyDC/PyDC/__init__.py new file mode 100644 index 00000000..be5148dd --- /dev/null +++ b/PyDC/PyDC/__init__.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python2 +# coding: utf-8 + +""" + Python dragon 32 converter + ========================== + + :copyleft: 2013 by Jens Diemer + :license: GNU GPL v3 or above, see LICENSE for more details. +""" + + +__version__ = (0, 1, 0, 'dev') +VERSION_STRING = '.'.join(str(part) for part in __version__) + +TITLE_LINE = "PyDC v%s copyleft 2013 by htfx.de - Jens Diemer, GNU GPL v3 or above" % VERSION_STRING + + +if __name__ == "__main__": +# import doctest +# print doctest.testmod( +# verbose=False +# # verbose=True +# ) + print TITLE_LINE diff --git a/PyDC/PyDC/base_cli.py b/PyDC/PyDC/base_cli.py new file mode 100644 index 00000000..5ebcb21d --- /dev/null +++ b/PyDC/PyDC/base_cli.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python2 +# coding: utf-8 + +""" + base commandline interface + ========================== + + :copyleft: 2013 by Jens Diemer + :license: GNU GPL v3 or above, see LICENSE for more details. +""" + +import argparse +import logging +import os +import sys + + +def get_log_levels(): + levels = [level for level in logging._levelNames if isinstance(level, int)] + return levels + +LOG_LEVELS = get_log_levels() + + +class Base_CLI(object): + LOG_NAME = None + DESCRIPTION = None + EPOLOG = None + VERSION = None + LOG_FORMATTER = logging.Formatter("%(asctime)s %(message)s") + + def __init__(self): + self.logfilename = None + print "logger name:", self.LOG_NAME + self.log = logging.getLogger(self.LOG_NAME) + + arg_kwargs = {} + if self.DESCRIPTION is not None: + arg_kwargs["description"] = self.DESCRIPTION + if self.EPOLOG is not None: + arg_kwargs["epilog"] = self.EPOLOG + if self.VERSION is not None: + arg_kwargs["version"] = self.VERSION + + self.parser = argparse.ArgumentParser(**arg_kwargs) + + self.parser.add_argument( + "--verbosity", type=int, choices=LOG_LEVELS, default=logging.WARNING, + help=( + "verbosity level to stdout (lower == more output!)" + " (default: %s)" % logging.WARNING + ) + ) + self.parser.add_argument( + "--logfile", type=int, choices=LOG_LEVELS, default=logging.INFO, + help=( + "verbosity level to log file (lower == more output!)" + " (default: %s)" % logging.INFO + ) + ) + + def parse_args(self): + if self.DESCRIPTION is not None: + print + print self.DESCRIPTION + print "-"*79 + print + + args = self.parser.parse_args() + + for arg, value in sorted(vars(args).items()): + self.log.debug("argument %s: %r", arg, value) + + return args + + def setup_logging(self, args): + self.verbosity = args.verbosity + self.logfile = args.logfile + + verbosity_level_name = logging.getLevelName(self.verbosity) + + logfile_level_name = logging.getLevelName(self.logfile) + + highest_level = min([self.logfile, self.verbosity]) + print "set log level to:", highest_level + self.log.setLevel(highest_level) + + if self.logfile > 0 and self.logfilename: + handler = logging.FileHandler(self.logfilename, mode='w', encoding="utf8") +# handler.set_level(self.logfile) + handler.level = self.logfile + handler.setFormatter(self.LOG_FORMATTER) + self.log.addHandler(handler) + + if self.verbosity > 0: + handler = logging.StreamHandler() +# handler.set_level(self.verbosity) + handler.level = self.verbosity + handler.setFormatter(self.LOG_FORMATTER) + self.log.addHandler(handler) + + self.log.info("Verbosity log level: %s" % verbosity_level_name) + self.log.info("logfile log level: %s" % logfile_level_name) + +if __name__ == "__main__": + import doctest + print doctest.testmod( + verbose=False + # verbose=True + ) diff --git a/PyDC/basic_tokens.py b/PyDC/PyDC/basic_tokens.py similarity index 100% rename from PyDC/basic_tokens.py rename to PyDC/PyDC/basic_tokens.py diff --git a/PyDC/configs.py b/PyDC/PyDC/configs.py similarity index 100% rename from PyDC/configs.py rename to PyDC/PyDC/configs.py diff --git a/PyDC/utils.py b/PyDC/PyDC/utils.py similarity index 99% rename from PyDC/utils.py rename to PyDC/PyDC/utils.py index f4e6ca72..836a071a 100755 --- a/PyDC/utils.py +++ b/PyDC/PyDC/utils.py @@ -22,6 +22,7 @@ 2: logging.INFO, 3: logging.DEBUG } +LOG_LEVELS = sorted(LOG_LEVEL_DICT.keys()) def human_duration(t): diff --git a/PyDC/wave2bitstream.py b/PyDC/PyDC/wave2bitstream.py similarity index 98% rename from PyDC/wave2bitstream.py rename to PyDC/PyDC/wave2bitstream.py index 671a6055..9eed82ea 100644 --- a/PyDC/wave2bitstream.py +++ b/PyDC/PyDC/wave2bitstream.py @@ -68,7 +68,11 @@ def __init__(self, wave_filename, self.avg_count = avg_count print "open wave file '%s'..." % wave_filename - self.wavefile = wave.open(wave_filename, "rb") + try: + self.wavefile = wave.open(wave_filename, "rb") + except IOError, err: + log.error("Error opening %s: %s" % (repr(wave_filename), err)) + sys.exit(-1) self.framerate = self.wavefile.getframerate() # frames / second print "Framerate:", self.framerate diff --git a/PyDC/PyDC_cli.py b/PyDC/PyDC_cli.py new file mode 100755 index 00000000..a5007bd0 --- /dev/null +++ b/PyDC/PyDC_cli.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python +# coding: utf-8 + +""" + Python dragon 32 converter - commandline interface + ================================================== + + :copyleft: 2013 by Jens Diemer + :license: GNU GPL v3 or above, see LICENSE for more details. +""" + +import logging +import os +import sys + +from PyDC import TITLE_LINE, VERSION_STRING +from PyDC.CassetteObjects import Cassette +from PyDC.PyDC import BitstreamHandler +from PyDC.configs import Dragon32Config +from PyDC.wave2bitstream import Wave2Bitstream +from PyDC.base_cli import Base_CLI + + +log = logging.getLogger("PyDC") + + +class PyDC_CLI(Base_CLI): + LOG_NAME = "PyDC" + DESCRIPTION = "Python dragon 32 converter" + EPOLOG = TITLE_LINE + VERSION = VERSION_STRING + LOG_FORMATTER = logging.Formatter("%(message)s") # %(asctime)s %(message)s") + + def __init__(self): + super(PyDC_CLI, self).__init__() + + self.parser.add_argument("src", help="Source filename (.wav/.bas)") + self.parser.add_argument("dst", help="Destination filename (.wav/.bas)") + + # For Wave2Bitstream(): + self.parser.add_argument( + "--hz_variation", type=int, default=450, + help=( + "How much Hz can signal scatter to match 1 or 0 bit ?" + " (default: 450)" + ) + ) + self.parser.add_argument( + "--min_volume_ratio", type=int, default=5, + help="percent volume to ignore sample (default: 5)" + ) + self.parser.add_argument( + "--avg_count", type=int, default=0, + help=( + "How many samples should be merged into a average value?" + " (default: 0)" + ) + ) + self.parser.add_argument( + "--end_count", type=int, default=2, + help=( + "Sample count that must be pos/neg at once" + " (default: 2)" + ) + ) + self.parser.add_argument( + "--mid_count", type=int, default=1, + help=( + "Sample count that can be around null" + " (default: 1)" + ) + ) + + def parse_args(self): + args = super(PyDC_CLI, self).parse_args() + + self.source_file = args.src + print "source file.......: %s" % self.source_file + + self.destination_file = args.dst + print "destination file..: %s" % self.destination_file + + return args + + def run(self): + self.args = self.parse_args() + + source_filename, source_ext = os.path.splitext(self.source_file) + dest_filename, dest_ext = os.path.splitext(self.destination_file) + + source_ext = source_ext.lower() + dest_ext = dest_ext.lower() + + self.logfilename = dest_filename + ".log" + self.setup_logging(self.args) + + self.d32cfg = Dragon32Config() + + if source_ext.startswith(".wav") and dest_ext.startswith(".bas"): + self.wav2bas() + elif source_ext.startswith(".bas") and dest_ext.startswith(".wav"): + self.bas2wav() + else: + print "ERROR:" + print "%s to %s ???" % (repr(self.source_file), repr(self.destination_file)) + sys.exit(-1) + + def bas2wav(self): + raise NotImplementedError("TBD") + # Create a bitstream from a existing .bas file: + # c.add_from_bas("test_files/HelloWorld1.bas") + # c.add_from_bas("test_files/Dragon Data Ltd - Examples from the Manual - 39~58 [run].bas") + # c.add_from_bas("test_files/LineNumberTest.bas") + # c.print_debug_info() + # bitstream = c.get_as_bitstream() + + def wav2bas(self): + # get bitstream from WAVE file: + st = Wave2Bitstream(self.source_file, + bit_nul_hz=1200, # "0" is a single cycle at 1200 Hz + bit_one_hz=2400, # "1" is a single cycle at 2400 Hz + hz_variation=self.args.hz_variation, # How much Hz can signal scatter to match 1 or 0 bit ? + + min_volume_ratio=self.args.min_volume_ratio, # percent volume to ignore sample + avg_count=self.args.avg_count, # How many samples should be merged into a average value? + end_count=self.args.end_count, # Sample count that must be pos/neg at once + mid_count=self.args.mid_count # Sample count that can be around null + ) + bitstream = iter(st) + + bh = BitstreamHandler(self.d32cfg) + bh.feed(bitstream) + bh.cassette.save_bas(self.destination_file) + + +if __name__ == "__main__": +# import doctest +# print doctest.testmod( +# verbose=False +# # verbose=True +# ) + + sys.argv.append("--help") + +# sys.argv.append("test_files/HelloWorld1 origin.wav") +# sys.argv.append("HelloWorld1 origin.bas") + + cli = PyDC_CLI() + cli.run() + + print "\n --- END --- \n" diff --git a/PyDC/README.creole b/PyDC/README.creole index 2104b18f..ee1f5f06 100644 --- a/PyDC/README.creole +++ b/PyDC/README.creole @@ -7,6 +7,51 @@ Convert dragon 32 Cassetts WAV files into plain text. copyleft: 2013 by Jens Diemer license: GNU GPL v3 or above, see LICENSE for more details. +=== PyDC_cli.py - usage + +{{{ +usage: PyDC_cli.py [-h] [-v] [--verbosity {0,10,20,30,40,50}] + [--logfile {0,10,20,30,40,50}] + [--hz_variation HZ_VARIATION] + [--min_volume_ratio MIN_VOLUME_RATIO] + [--avg_count AVG_COUNT] [--end_count END_COUNT] + [--mid_count MID_COUNT] + src dst + +Python dragon 32 converter + +positional arguments: + src Source filename (.wav/.bas) + dst Destination filename (.wav/.bas) + +optional arguments: + -h, --help show this help message and exit + -v, --version show program's version number and exit + --verbosity {0,10,20,30,40,50} + verbosity level to stdout (lower == more output!) + (default: 30) + --logfile {0,10,20,30,40,50} + verbosity level to log file (lower == more output!) + (default: 20) + --hz_variation HZ_VARIATION + How much Hz can signal scatter to match 1 or 0 bit ? + (default: 450) + --min_volume_ratio MIN_VOLUME_RATIO + percent volume to ignore sample (default: 5) + --avg_count AVG_COUNT + How many samples should be merged into a average + value? (default: 0) + --end_count END_COUNT + Sample count that must be pos/neg at once (default: 2) + --mid_count MID_COUNT + Sample count that can be around null (default: 1) +}}} + +Example: +{{{ +~$ python PyDC_cli.py FooBar.wav FooBar.bas +}}} + === Links From e4a87737c585bc0e458ac8326ef49a5725dec065 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Wed, 28 Aug 2013 12:01:50 +0200 Subject: [PATCH 041/151] bigger Sync-Byte search sizes, e.g. for: "Dragon Data Ltd - Examples from the Manual - 39~58 [run].wav" --- PyDC/PyDC/PyDC.py | 6 +++--- PyDC/PyDC/configs.py | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/PyDC/PyDC/PyDC.py b/PyDC/PyDC/PyDC.py index e061cf4b..afc47264 100755 --- a/PyDC/PyDC/PyDC.py +++ b/PyDC/PyDC/PyDC.py @@ -174,14 +174,14 @@ def sync_bitstream(self, bitstream): # Search for sync-byte sync_pattern = list(codepoints2bitstream(self.cfg.SYNC_BYTE_CODEPOINT)) - max_pos = (self.cfg.LEAD_BYTE_LEN + 2) * 8 + max_search_bits = self.cfg.MAX_SYNC_BYTE_SEARCH * 8 try: - sync_pos = find_iter_window(bitstream, sync_pattern, max_pos) + sync_pos = find_iter_window(bitstream, sync_pattern, max_search_bits) except MaxPosArraived, err: raise SyncByteNotFoundError( "Error: Sync-Byte '%s' (%s) not found in the first %i Bytes! (%s)" % ( list2str(sync_pattern), hex(self.cfg.SYNC_BYTE_CODEPOINT), - self.cfg.LEAD_BYTE_LEN, err + self.cfg.MAX_SYNC_BYTE_SEARCH, err ) ) except PatternNotFound, err: diff --git a/PyDC/PyDC/configs.py b/PyDC/PyDC/configs.py index 5c1a4ffd..e9555819 100644 --- a/PyDC/PyDC/configs.py +++ b/PyDC/PyDC/configs.py @@ -64,6 +64,7 @@ class Dragon32Config(BaseConfig): LEAD_BYTE_CODEPOINT = 0x55 # 10101010 LEAD_BYTE_LEN = 255 SYNC_BYTE_CODEPOINT = 0x3C # 00111100 + MAX_SYNC_BYTE_SEARCH = 600 # search size in **Bytes** # Block types: FILENAME_BLOCK = 0x00 From 4f9cbfc66dd4bf20be9f51089eb4fab585f0484a Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Wed, 28 Aug 2013 12:02:25 +0200 Subject: [PATCH 042/151] Display and check the block checksum --- PyDC/PyDC/CassetteObjects.py | 46 +++++++++--------- PyDC/PyDC/PyDC.py | 93 +++++++++++++++++------------------- PyDC/PyDC/utils.py | 16 +++++-- 3 files changed, 79 insertions(+), 76 deletions(-) diff --git a/PyDC/PyDC/CassetteObjects.py b/PyDC/PyDC/CassetteObjects.py index 23347134..32339912 100644 --- a/PyDC/PyDC/CassetteObjects.py +++ b/PyDC/PyDC/CassetteObjects.py @@ -11,16 +11,16 @@ :license: GNU GPL v3 or above, see LICENSE for more details. """ -import itertools import logging import os +import sys # own modules from basic_tokens import bytes2codeline from configs import Dragon32Config from utils import get_word, codepoints2string, string2codepoint, LOG_LEVEL_DICT, \ - LOG_FORMATTER, codepoints2bitstream, pprint_codepoints -import sys + LOG_FORMATTER, codepoints2bitstream, pformat_codepoints + log = logging.getLogger("PyDC") @@ -142,9 +142,11 @@ def add_block_data(self, block_length, data): # # print repr(data) # print_as_hex_list(data) # print_codepoint_stream(data) -# data = iter(data) # sys.exit() + # create from codepoint list a iterator + data = iter(data) + byte_count = 0 while True: try: @@ -289,24 +291,19 @@ def create_from_bas(self, filename, file_content): self.file_content = FileContent(self.cfg) self.file_content.create_from_bas(file_content) - def create_from_wave(self, block_codepoints): + def create_from_wave(self, codepoints): - block_codepoints = list(block_codepoints) - print "filename data:", - pprint_codepoints(block_codepoints) - block_codepoints = iter(block_codepoints) + log.debug("filename data: %s" % pformat_codepoints(codepoints)) - raw_filename = list(itertools.islice(block_codepoints, 8)) + raw_filename = codepoints[:8] self.filename = codepoints2string(raw_filename).rstrip() print "\nFilename: %s" % repr(self.filename) - codepoints = list(block_codepoints) - # print "file meta:" # print_codepoint_stream(codepoints) - self.file_type = codepoints[0] + self.file_type = codepoints[9] if not self.file_type in self.cfg.FILETYPE_DICT: raise NotImplementedError( @@ -320,7 +317,7 @@ def create_from_wave(self, block_codepoints): elif self.file_type == self.cfg.FTYPE_BIN: raise NotImplementedError("Binary files are not supported, yet.") - ascii_flag = codepoints[1] + ascii_flag = codepoints[10] print "ASCII Flag is:", repr(ascii_flag) if ascii_flag == self.cfg.BASIC_TOKENIZED: self.is_tokenized = True @@ -366,7 +363,7 @@ class Cassette(object): """ >>> d32cfg = Dragon32Config() >>> c = Cassette(d32cfg) - >>> c.add_from_bas("test_files/HelloWorld1.bas") + >>> c.add_from_bas("../test_files/HelloWorld1.bas") >>> c.print_debug_info() # doctest: +NORMALIZE_WHITESPACE There exists 1 files: Filename: 'HELLOWOR' @@ -449,7 +446,7 @@ def block2codepoint_stream(self, block_type, block_codepoints): log.debug("\nyield %s" % self.cfg.BLOCK_TYPE_DICT[block_type]) print "-"*79 block_codepoints = list(block_codepoints) - pprint_codepoints(block_codepoints) + print pformat_codepoints(block_codepoints) block_codepoints = iter(block_codepoints) print "-"*79 for codepoint in block_codepoints: @@ -516,6 +513,14 @@ def pprint_codepoint_stream(self): if __name__ == "__main__": +# import doctest +# print doctest.testmod( +# verbose=False +# # verbose=True +# ) +# sys.exit() + + log_level = LOG_LEVEL_DICT[3] log.setLevel(log_level) @@ -525,14 +530,9 @@ def pprint_codepoint_stream(self): d32cfg = Dragon32Config() c = Cassette(d32cfg) - c.add_from_bas("test_files/HelloWorld1.bas") + c.add_from_bas("../test_files/HelloWorld1.bas") c.print_debug_info() # print list(c.codepoint_stream()) print list(c.get_as_bitstream()) - import doctest - print doctest.testmod( - verbose=False - # verbose=True - ) -# sys.exit() + diff --git a/PyDC/PyDC/PyDC.py b/PyDC/PyDC/PyDC.py index afc47264..01b77409 100755 --- a/PyDC/PyDC/PyDC.py +++ b/PyDC/PyDC/PyDC.py @@ -6,10 +6,8 @@ ==================================== TODO: - - test block checksum, see: - http://archive.worldofdragon.org/phpBB3/viewtopic.php?f=8&t=4231&p=8905#p8905 - - add cli - write .BAS file + - create GUI :copyleft: 2013 by Jens Diemer :license: GNU GPL v3 or above, see LICENSE for more details. @@ -18,17 +16,20 @@ import itertools import logging +import os + +# own modules from configs import Dragon32Config from CassetteObjects import Cassette + log = logging.getLogger("PyDC") # own modules from utils import find_iter_window, iter_steps, MaxPosArraived, \ print_bitlist, bits2codepoint, list2str, bitstream2codepoints, \ - PatternNotFound, LOG_FORMATTER, codepoints2bitstream, \ - pprint_codepoints + PatternNotFound, LOG_FORMATTER, codepoints2bitstream, pformat_codepoints from wave2bitstream import Wave2Bitstream @@ -103,19 +104,11 @@ def feed(self, bitstream): # bitstream = iter(bitstream) try: - block_type, block_length, codepoint_stream = self.get_block_info(bitstream) + block_type, block_length, codepoints, checksum = self.get_block_info(bitstream) except SyncByteNotFoundError, err: log.error(err) break - codepoint_stream = list(codepoint_stream) - print "\n***** codepoint_stream length:", len(codepoint_stream) - pprint_codepoints(codepoint_stream) - print " -"*40 - codepoint_stream = iter(codepoint_stream) - - print "*** block length:", block_length - try: block_type_name = self.cfg.BLOCK_TYPE_DICT[block_type] except KeyError: @@ -141,18 +134,15 @@ def feed(self, bitstream): print "-"*79 break - # block_codepoints = bitstream2codepoints(block_bits) - - # block_codepoints = list(block_codepoints) - # print_codepoint_stream(block_codepoints) - # block_codepoints = iter(block_codepoints) - - self.cassette.add_block(block_type, block_length, codepoint_stream) + self.cassette.add_block(block_type, block_length, codepoints) print "="*79 def sync_bitstream(self, bitstream): bitstream.sync(32) # Sync bitstream to wave sinus cycle +# test_bitstream = list(itertools.islice(bitstream, 258 * 8)) +# print_bitlist(test_bitstream) + # Searching for lead-in byte lead_in_pattern = list(codepoints2bitstream(self.cfg.LEAD_BYTE_CODEPOINT)) max_pos = self.cfg.LEAD_BYTE_LEN * 8 @@ -199,38 +189,42 @@ def sync_bitstream(self, bitstream): def get_block_info(self, bitstream): -# print "-"*79 -# bitstream = list(bitstream) -# print_bitlist(bitstream, no_repr=True) -# bitstream = iter(bitstream) -# print "-"*79 - - # print "-"*79 - # print_bitlist(bitstream, no_repr=True) - # print "-"*79 - # sys.exit() - self.sync_bitstream(bitstream) # Sync bitstream with SYNC_BYTE - # print "-"*79 - # bitstream = list(bitstream) - # print_bitlist(bitstream) - # bitstream = iter(bitstream) - # print "-"*79 - + # convert the raw bitstream to codepoint stream codepoint_stream = bitstream2codepoints(bitstream) - # print "-"*79 - # print_codepoint_stream(codepoint_stream) - # print "-"*79 block_type = next(codepoint_stream) - # print "raw block type:", repr(block_type), hex(block_type) + log.info("raw block type: %s (%s)" % (hex(block_type), repr(block_type))) + block_length = next(codepoint_stream) - # print "raw block length:", repr(block_length) - codepoint_stream = itertools.islice(codepoint_stream, block_length) + # Get the complete block content + codepoints = list(itertools.islice(codepoint_stream, block_length)) + + real_block_len = len(codepoints) + if real_block_len == block_length: + log.info("Block length: %sBytes, ok." % block_length) + else: + log.error("Block should be %sBytes but are: %sBytes!" % (block_length, real_block_len)) + + # Check block checksum + + origin_checksum = next(codepoint_stream) - return block_type, block_length, codepoint_stream + calc_checksum = sum([codepoint for codepoint in codepoints]) + calc_checksum += block_type + calc_checksum += block_length + calc_checksum = calc_checksum & 0xFF + + if calc_checksum == origin_checksum: + log.info("Block checksum %s is ok." % hex(origin_checksum)) + else: + log.error("Block checksum %s is not equal with calculated checksum: %s" % ( + hex(origin_checksum), hex(calc_checksum) + )) + + return block_type, block_length, codepoints, origin_checksum @@ -288,7 +282,7 @@ def print_bit_list_stats(bit_list): # FILENAME = "Quickbeam Software - Duplicas v3.0.wav" # binary! -# FILENAME = "Dragon Data Ltd - Examples from the Manual - 39~58 [run].wav" + FILENAME = "Dragon Data Ltd - Examples from the Manual - 39~58 [run].wav" # Bit 1 min: 1696Hz avg: 2004.0Hz max: 2004Hz variation: 308Hz # Bit 0 min: 1025Hz avg: 1025.0Hz max: 1025Hz Variation: 0Hz # 155839 Bits: 73776 positive bits and 82063 negative bits @@ -297,7 +291,7 @@ def print_bit_list_stats(bit_list): # FILENAME = "2_DBJ.WAV" # TODO # BASIC file with high line numbers: - FILENAME = "LineNumber Test 01.wav" # tokenized BASIC - no sync +# FILENAME = "LineNumber Test 01.wav" # tokenized BASIC - no sync # FILENAME = "LineNumber Test 02.wav" # ASCII BASIC - no sync @@ -321,8 +315,11 @@ def print_bit_list_stats(bit_list): d32cfg = Dragon32Config() c = Cassette(d32cfg) +# filepath = os.path.abspath("../test_files/%s" % FILENAME) + filepath = "../test_files/%s" % FILENAME + # get bitstream from WAVE file: - st = Wave2Bitstream("test_files/%s" % FILENAME, + st = Wave2Bitstream(filepath, bit_nul_hz=1200, # "0" is a single cycle at 1200 Hz bit_one_hz=2400, # "1" is a single cycle at 2400 Hz hz_variation=450, # How much Hz can signal scatter to match 1 or 0 bit ? diff --git a/PyDC/PyDC/utils.py b/PyDC/PyDC/utils.py index 836a071a..f53a153f 100755 --- a/PyDC/PyDC/utils.py +++ b/PyDC/PyDC/utils.py @@ -558,10 +558,11 @@ def print_as_hex_list(codepoint_stream): """ print ",".join([hex(codepoint) for codepoint in codepoint_stream]) -def pprint_codepoints(codepoints): +def pformat_codepoints(codepoints): """ - >>> pprint_codepoints([13, 70, 111, 111, 32, 66, 97, 114, 32, 33, 13]) - ['\r', 'Foo Bar !', '\r'] + >>> l = pformat_codepoints([13, 70, 111, 111, 32, 66, 97, 114, 32, 33, 13]) + >>> repr(l) + "['\\r', 'Foo Bar !', '\\r']" """ printable = string.printable.replace("\n", "").replace("\r", "") line = [] @@ -575,7 +576,7 @@ def pprint_codepoints(codepoints): line.append(strings) strings = "" line.append(char) - print line + return line def print_block_bit_list(block_bit_list, display_block_count=8, no_repr=False): """ @@ -659,7 +660,12 @@ def get_word(byte_iterator): >>> hex(v) '0x1e12' """ - return (next(byte_iterator) << 8) | next(byte_iterator) + byte_values = list(itertools.islice(byte_iterator, 2)) + try: + word = (byte_values[0] << 8) | byte_values[1] + except TypeError, err: + raise TypeError("Can't build word from %s: %s" % (repr(byte_values), err)) + return word def codepoints2string(codepoints): """ From 2e19154210d80e3f72e0d596c16300381b246754 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Wed, 28 Aug 2013 17:48:45 +0200 Subject: [PATCH 043/151] code cleanup: * move some settings into config files * create a wav2bad() function and use it in CLI --- PyDC/PyDC/PyDC.py | 14 ------- PyDC/PyDC/__init__.py | 36 ++++++++++++++++++ PyDC/PyDC/configs.py | 24 +++++++++--- PyDC/PyDC/wave2bitstream.py | 73 +++++++++++++++---------------------- PyDC/PyDC_cli.py | 46 ++++++----------------- 5 files changed, 95 insertions(+), 98 deletions(-) diff --git a/PyDC/PyDC/PyDC.py b/PyDC/PyDC/PyDC.py index 01b77409..a17883d5 100755 --- a/PyDC/PyDC/PyDC.py +++ b/PyDC/PyDC/PyDC.py @@ -250,8 +250,6 @@ def print_bit_list_stats(bit_list): print "%i positive bits and %i negative bits" % (positive_count, negative_count) - - if __name__ == "__main__": import doctest print doctest.testmod( @@ -264,18 +262,9 @@ def print_bit_list_stats(bit_list): # created by Xroar Emulator # FILENAME = "HelloWorld1 xroar.wav" # 8Bit 22050Hz -# Bit 1 min: 1696Hz avg: 2058.3Hz max: 2205Hz variation: 509Hz -# Bit 0 min: 595Hz avg: 1090.4Hz max: 1160Hz Variation: 565Hz -# 4760 Bits: 2243 positive bits and 2517 negative bits - - # created by origin Dragon 32 machine # FILENAME = "HelloWorld1 origin.wav" # 16Bit 44.1KHz mono - # Bit 1 min: 1764Hz avg: 2013.9Hz max: 2100Hz variation: 336Hz - # Bit 0 min: 595Hz avg: 1090.2Hz max: 1336Hz Variation: 741Hz - # 2710 Bits: 1217 positive bits and 1493 negative bits - # Test files from: # http://archive.worldofdragon.org/archive/index.php?dir=Tapes/Dragon/wav/ @@ -283,9 +272,6 @@ def print_bit_list_stats(bit_list): FILENAME = "Dragon Data Ltd - Examples from the Manual - 39~58 [run].wav" - # Bit 1 min: 1696Hz avg: 2004.0Hz max: 2004Hz variation: 308Hz - # Bit 0 min: 1025Hz avg: 1025.0Hz max: 1025Hz Variation: 0Hz - # 155839 Bits: 73776 positive bits and 82063 negative bits # FILENAME = "1_MANIA.WAV" # 148579 frames, 4879 bits (raw) # FILENAME = "2_DBJ.WAV" # TODO diff --git a/PyDC/PyDC/__init__.py b/PyDC/PyDC/__init__.py index be5148dd..da6ec817 100644 --- a/PyDC/PyDC/__init__.py +++ b/PyDC/PyDC/__init__.py @@ -9,6 +9,9 @@ :license: GNU GPL v3 or above, see LICENSE for more details. """ +from wave2bitstream import Wave2Bitstream +from PyDC import BitstreamHandler + __version__ = (0, 1, 0, 'dev') VERSION_STRING = '.'.join(str(part) for part in __version__) @@ -16,6 +19,29 @@ TITLE_LINE = "PyDC v%s copyleft 2013 by htfx.de - Jens Diemer, GNU GPL v3 or above" % VERSION_STRING +def bas2wav(self): + raise NotImplementedError("TBD") + # Create a bitstream from a existing .bas file: +# c.add_from_bas("test_files/HelloWorld1.bas") +# c.add_from_bas("test_files/Dragon Data Ltd - Examples from the Manual - 39~58 [run].bas") +# c.add_from_bas("test_files/LineNumberTest.bas") +# c.print_debug_info() +# bitstream = c.get_as_bitstream() + + +def wav2bas(source_filepath, destination_filepath, cfg): + # get bitstream generator from WAVE file: + bitstream = iter(Wave2Bitstream(source_filepath, cfg)) + + # store bitstream into python objects + bh = BitstreamHandler(cfg) + bh.feed(bitstream) + + # save .bas file + bh.cassette.save_bas(destination_filepath) + + + if __name__ == "__main__": # import doctest # print doctest.testmod( @@ -23,3 +49,13 @@ # # verbose=True # ) print TITLE_LINE + + from configs import Dragon32Config + cfg = Dragon32Config() + wav2bas( + "../test_files/HelloWorld1 origin.wav", + "../HelloWorld1 origin.bas", + cfg + ) + + print "\n --- END ---" diff --git a/PyDC/PyDC/configs.py b/PyDC/PyDC/configs.py index e9555819..e5cc5b89 100644 --- a/PyDC/PyDC/configs.py +++ b/PyDC/PyDC/configs.py @@ -36,12 +36,18 @@ def print_debug_info(self): class Dragon32Config(BaseConfig): """ >>> d32cfg = Dragon32Config() - >>> d32cfg.print_debug_info() # doctest: +NORMALIZE_WHITESPACE + >>> d32cfg.print_debug_info() # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS Config: 'Dragon32Config' + AVG_COUNT = 0 (in hex: '0x0' - binary: 00000000) + BASIC_ASCII = 255 (in hex: '0xff' - binary: 11111111) + BASIC_CODE_END = [0, 0] + BASIC_TOKENIZED = 0 (in hex: '0x0' - binary: 00000000) + BASIC_TYPE_DICT = {0: 'tokenized BASIC (0x00)', 255: 'ASCII BASIC (0xff)'} BIT_NUL_HZ = 1200 (in hex: '0x4b0' - binary: 00001101001) BIT_ONE_HZ = 2400 (in hex: '0x960' - binary: 000001101001) BLOCK_TYPE_DICT = {0: 'filename block (0x00)', 1: 'data block (0x01)', 255: 'end-of-file block (0xff)'} DATA_BLOCK = 1 (in hex: '0x1' - binary: 10000000) + END_COUNT = 2 (in hex: '0x2' - binary: 01000000) EOF_BLOCK = 255 (in hex: '0xff' - binary: 11111111) FILENAME_BLOCK = 0 (in hex: '0x0' - binary: 00000000) FILETYPE_DICT = {0: 'BASIC programm (0x00)', 1: 'Data file (0x01)', 255: 'Binary file (0xFF)'} @@ -51,16 +57,21 @@ class Dragon32Config(BaseConfig): HZ_VARIATION = 450 (in hex: '0x1c2' - binary: 010000111) LEAD_BYTE_CODEPOINT = 85 (in hex: '0x55' - binary: 10101010) LEAD_BYTE_LEN = 255 (in hex: '0xff' - binary: 11111111) + MAX_SYNC_BYTE_SEARCH = 600 (in hex: '0x258' - binary: 0001101001) + MID_COUNT = 1 (in hex: '0x1' - binary: 10000000) + MIN_VOLUME_RATIO = 5 (in hex: '0x5' - binary: 10100000) SYNC_BYTE_CODEPOINT = 60 (in hex: '0x3c' - binary: 00111100) - - >>> ",".join([hex(c) for c in d32cfg.get_header_codepoint_stream()]) - ... # doctest: +ELLIPSIS - '0x55,0x55,0x55,...,0x55,0x55,0x55,0x3c' """ + BIT_NUL_HZ = 1200 # "0" is a single cycle at 1200 Hz BIT_ONE_HZ = 2400 # "1" is a single cycle at 2400 Hz HZ_VARIATION = 450 # How much Hz can signal scatter to match 1 or 0 bit ? + MIN_VOLUME_RATIO = 5 # percent volume to ignore sample + AVG_COUNT = 0 # How many samples should be merged into a average value? + END_COUNT = 2 # Sample count that must be pos/neg at once + MID_COUNT = 1 # Sample count that can be around null + LEAD_BYTE_CODEPOINT = 0x55 # 10101010 LEAD_BYTE_LEN = 255 SYNC_BYTE_CODEPOINT = 0x3C # 00111100 @@ -70,13 +81,13 @@ class Dragon32Config(BaseConfig): FILENAME_BLOCK = 0x00 DATA_BLOCK = 0x01 EOF_BLOCK = 0xff - BLOCK_TYPE_DICT = { FILENAME_BLOCK: "filename block (0x00)", DATA_BLOCK: "data block (0x01)", EOF_BLOCK: "end-of-file block (0xff)", } + # File types: FTYPE_BASIC = 0x00 FTYPE_DATA = 0x01 FTYPE_BIN = 0xff @@ -86,6 +97,7 @@ class Dragon32Config(BaseConfig): FTYPE_BIN:"Binary file (0xFF)", } + # Basic format types: BASIC_TOKENIZED = 0x00 BASIC_ASCII = 0xff BASIC_TYPE_DICT = { diff --git a/PyDC/PyDC/wave2bitstream.py b/PyDC/PyDC/wave2bitstream.py index 9eed82ea..f33f2f20 100644 --- a/PyDC/PyDC/wave2bitstream.py +++ b/PyDC/PyDC/wave2bitstream.py @@ -26,7 +26,7 @@ from utils import average, diff_info, TextLevelMeter, iter_window, \ human_duration, ProcessInfo, LOG_LEVEL_DICT, LOG_FORMATTER, print_bitlist, \ count_sign, iter_steps - +from configs import Dragon32Config log = logging.getLogger("PyDC") @@ -50,22 +50,12 @@ class Wave2Bitstream(object): 4: 2147483647, # 32-bit wave file } - def __init__(self, wave_filename, - bit_nul_hz, # sinus cycle frequency in Hz for one "0" bit - bit_one_hz, # sinus cycle frequency in Hz for one "1" bit - hz_variation, # How much Hz can signal scatter to match 1 or 0 bit ? - min_volume_ratio=5, # percent volume to ignore sample - avg_count=0, # How many samples should be merged into a average value? - end_count=2, # Sample count that must be pos/neg at once - mid_count=1 # Sample count that can be around null - ): + def __init__(self, wave_filename, cfg): self.wave_filename = wave_filename + self.cfg = cfg - assert end_count > 0 - assert mid_count > 0 - self.end_count = end_count - self.mid_count = mid_count - self.avg_count = avg_count + assert cfg.END_COUNT > 0 # Sample count that must be pos/neg at once + assert cfg.MID_COUNT > 0 # Sample count that can be around null print "open wave file '%s'..." % wave_filename try: @@ -90,15 +80,15 @@ def __init__(self, wave_filename, self.max_value = self.MAX_VALUES[self.samplewidth] print "the max volume value is:", self.max_value - self.min_volume = int(round(self.max_value * min_volume_ratio / 100)) - print "Ignore sample lower than %.1f%% = %i" % (min_volume_ratio, self.min_volume) + self.min_volume = int(round(self.max_value * cfg.MIN_VOLUME_RATIO / 100)) + print "Ignore sample lower than %.1f%% = %i" % (cfg.MIN_VOLUME_RATIO, self.min_volume) # build min/max Hz values - self.bit_nul_min_hz = bit_nul_hz - hz_variation - self.bit_nul_max_hz = bit_nul_hz + hz_variation + self.bit_nul_min_hz = cfg.BIT_NUL_HZ - cfg.HZ_VARIATION + self.bit_nul_max_hz = cfg.BIT_NUL_HZ + cfg.HZ_VARIATION - self.bit_one_min_hz = bit_one_hz - hz_variation - self.bit_one_max_hz = bit_one_hz + hz_variation + self.bit_one_min_hz = cfg.BIT_ONE_HZ - cfg.HZ_VARIATION + self.bit_one_max_hz = cfg.BIT_ONE_HZ + cfg.HZ_VARIATION print "bit-0 in %sHz - %sHz | bit-1 in %sHz - %sHz" % ( self.bit_nul_min_hz, self.bit_nul_max_hz, self.bit_one_min_hz, self.bit_one_max_hz, @@ -115,7 +105,7 @@ def __init__(self, wave_filename, # get frame numer + volume value from the WAVE file self.wave_values_generator = self.iter_wave_values() - if avg_count > 1: + if cfg.AVG_COUNT > 1: # merge samples to a average sample log.debug("Merge %s audio sample to one average sample" % self.avg_count) self.avg_wave_values_generator = self.iter_avg_wave_values(self.wave_values_generator) @@ -281,36 +271,41 @@ def iter_duration(self, iter_trigger): def iter_trigger(self, iter_wave_values): - window_size = (2 * self.end_count) + self.mid_count + """ + trigger middle crossing of the wave sinus curve + """ + window_size = (2 * self.cfg.END_COUNT) + self.cfg.MID_COUNT # sinus curve goes from negative into positive: - pos_null_transit = [(0, self.end_count), (self.end_count, 0)] + pos_null_transit = [(0, self.cfg.END_COUNT), (self.cfg.END_COUNT, 0)] # sinus curve goes from positive into negative: - neg_null_transit = [(self.end_count, 0), (0, self.end_count)] + neg_null_transit = [(self.cfg.END_COUNT, 0), (0, self.cfg.END_COUNT)] - if self.mid_count > 3: - mid_index = int(round(self.mid_count / 2.0)) + if self.cfg.MID_COUNT > 3: + mid_index = int(round(self.cfg.MID_COUNT / 2.0)) else: mid_index = 0 in_pos = False for values in iter_window(iter_wave_values, window_size): -# if self.frame_no > 20: sys.exit() -# print values - previous_values = values[:self.end_count] # e.g.: 123----- - mid_values = values[self.end_count:self.end_count + self.mid_count] # e.g.: ---45--- - next_values = values[-self.end_count:] # e.g.: -----678 + # Split the window + previous_values = values[:self.cfg.END_COUNT] # e.g.: 123----- + mid_values = values[self.cfg.END_COUNT:self.cfg.END_COUNT + self.cfg.MID_COUNT] # e.g.: ---45--- + next_values = values[-self.cfg.END_COUNT:] # e.g.: -----678 - # (frame_no, value) tuple -> value list + # get only the value and strip the frame_no + # e.g.: (frame_no, value) tuple -> value list previous_values = [i[1] for i in previous_values] next_values = [i[1] for i in next_values] + # Count sign from previous and next values sign_info = [ count_sign(previous_values, 0), count_sign(next_values, 0) ] + # yield the mid crossing if in_pos == False and sign_info == pos_null_transit: log.debug("sinus curve goes from negative into positive") # log.debug(" %s | %s | %s" % (previous_values, mid_values, next_values)) @@ -323,8 +318,6 @@ def iter_trigger(self, iter_wave_values): yield mid_values[mid_index][0] in_pos = False -# print - def iter_avg_wave_values(self, wave_values_generator): if self.avg_count == 3: mid_index = 1 @@ -443,15 +436,9 @@ def iter_wave_values(self): handler.setFormatter(LOG_FORMATTER) log.addHandler(handler) - st = Wave2Bitstream("test_files/%s" % FILENAME, - bit_nul_hz=1200, # "0" is a single cycle at 1200 Hz - bit_one_hz=2400, # "1" is a single cycle at 2400 Hz - hz_variation=450, # How much Hz can signal scatter to match 1 or 0 bit ? + d32cfg = Dragon32Config() -# avg_count=2, # How many samples should be merged into a average value? -# end_count=2, # Sample count that must be pos/neg at once -# mid_count=1 # Sample count that can be around null - ) + st = Wave2Bitstream("../test_files/%s" % FILENAME, d32cfg) bitstream = iter(st) bitstream.sync(32) diff --git a/PyDC/PyDC_cli.py b/PyDC/PyDC_cli.py index a5007bd0..ef80bb65 100755 --- a/PyDC/PyDC_cli.py +++ b/PyDC/PyDC_cli.py @@ -13,11 +13,8 @@ import os import sys -from PyDC import TITLE_LINE, VERSION_STRING -from PyDC.CassetteObjects import Cassette -from PyDC.PyDC import BitstreamHandler +from PyDC import TITLE_LINE, VERSION_STRING, wav2bas from PyDC.configs import Dragon32Config -from PyDC.wave2bitstream import Wave2Bitstream from PyDC.base_cli import Base_CLI @@ -94,44 +91,23 @@ def run(self): self.logfilename = dest_filename + ".log" self.setup_logging(self.args) - self.d32cfg = Dragon32Config() + self.cfg = Dragon32Config() + + self.cfg.HZ_VARIATION = self.args.hz_variation # How much Hz can signal scatter to match 1 or 0 bit ? + self.cfg.MIN_VOLUME_RATIO = self.args.min_volume_ratio # percent volume to ignore sample + self.cfg.AVG_COUNT = self.args.avg_count # How many samples should be merged into a average value? + self.cfg.END_COUNT = self.args.end_count # Sample count that must be pos/neg at once + self.cfg.MID_COUNT = self.args.mid_count # Sample count that can be around null if source_ext.startswith(".wav") and dest_ext.startswith(".bas"): - self.wav2bas() + wav2bas(self.source_file, self.destination_file, self.cfg) elif source_ext.startswith(".bas") and dest_ext.startswith(".wav"): - self.bas2wav() + raise NotImplementedError("TBD") else: print "ERROR:" print "%s to %s ???" % (repr(self.source_file), repr(self.destination_file)) sys.exit(-1) - def bas2wav(self): - raise NotImplementedError("TBD") - # Create a bitstream from a existing .bas file: - # c.add_from_bas("test_files/HelloWorld1.bas") - # c.add_from_bas("test_files/Dragon Data Ltd - Examples from the Manual - 39~58 [run].bas") - # c.add_from_bas("test_files/LineNumberTest.bas") - # c.print_debug_info() - # bitstream = c.get_as_bitstream() - - def wav2bas(self): - # get bitstream from WAVE file: - st = Wave2Bitstream(self.source_file, - bit_nul_hz=1200, # "0" is a single cycle at 1200 Hz - bit_one_hz=2400, # "1" is a single cycle at 2400 Hz - hz_variation=self.args.hz_variation, # How much Hz can signal scatter to match 1 or 0 bit ? - - min_volume_ratio=self.args.min_volume_ratio, # percent volume to ignore sample - avg_count=self.args.avg_count, # How many samples should be merged into a average value? - end_count=self.args.end_count, # Sample count that must be pos/neg at once - mid_count=self.args.mid_count # Sample count that can be around null - ) - bitstream = iter(st) - - bh = BitstreamHandler(self.d32cfg) - bh.feed(bitstream) - bh.cassette.save_bas(self.destination_file) - if __name__ == "__main__": # import doctest @@ -140,7 +116,7 @@ def wav2bas(self): # # verbose=True # ) - sys.argv.append("--help") +# sys.argv.append("--help") # sys.argv.append("test_files/HelloWorld1 origin.wav") # sys.argv.append("HelloWorld1 origin.bas") From 8b82d78abe662cff5d04d8e42d780a7786fb8b02 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Mon, 2 Sep 2013 18:08:40 +0200 Subject: [PATCH 044/151] add working state top master --- PyDC/PyDC/configs.py | 6 +- PyDC/PyDC/tests.py | 138 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 142 insertions(+), 2 deletions(-) create mode 100755 PyDC/PyDC/tests.py diff --git a/PyDC/PyDC/configs.py b/PyDC/PyDC/configs.py index e5cc5b89..9c6cff6c 100644 --- a/PyDC/PyDC/configs.py +++ b/PyDC/PyDC/configs.py @@ -63,8 +63,10 @@ class Dragon32Config(BaseConfig): SYNC_BYTE_CODEPOINT = 60 (in hex: '0x3c' - binary: 00111100) """ - BIT_NUL_HZ = 1200 # "0" is a single cycle at 1200 Hz - BIT_ONE_HZ = 2400 # "1" is a single cycle at 2400 Hz + # For reading WAVE files: + BIT_NUL_HZ = 1100 # Spec says: 1200Hz - Bit "0" is a single cycle at 1200 Hz + BIT_ONE_HZ = 2100 # Spec says: 2400Hz - Bit "1" is a single cycle at 2400 Hz + # see: http://five.pairlist.net/pipermail/coco/2013-August/070879.html HZ_VARIATION = 450 # How much Hz can signal scatter to match 1 or 0 bit ? MIN_VOLUME_RATIO = 5 # percent volume to ignore sample diff --git a/PyDC/PyDC/tests.py b/PyDC/PyDC/tests.py new file mode 100755 index 00000000..ee5d80a1 --- /dev/null +++ b/PyDC/PyDC/tests.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python2 +# coding: utf-8 + +""" + PyDC - unittests + ~~~~~~~~~~~~~~~~ + + :copyleft: 2013 by Jens Diemer + :license: GNU GPL v3 or above, see LICENSE for more details. +""" + +import logging +import os +import sys +import unittest + +# own modules +import configs +from __init__ import wav2bas + + +class TestDragon32Conversion(unittest.TestCase): + + def setUp(self): + self.base_path = os.path.normpath( + os.path.join(os.path.split(configs.__file__)[0], "..") + ) + self.cfg = configs.Dragon32Config() + + def _src_file_path(self, filename): + return os.path.relpath( + os.path.join(self.base_path, "test_files", filename) + ) + + def _dst_file_path(self, filename): + return os.path.relpath( + os.path.join(self.base_path, filename) + ) + + def _get_and_delete_dst(self, destination_filepath): + f = open(destination_filepath, "r") + dest_content = f.read() + f.close() + os.remove(destination_filepath) + return dest_content + + def test_wav2bas01(self): + source_filepath = self._src_file_path("HelloWorld1 xroar.wav") + destination_filepath = self._dst_file_path("unittest_wav2bas01.bas") + wav2bas(source_filepath, destination_filepath, self.cfg) + + # no filename used in CSAVE: + destination_filepath = self._dst_file_path("unittest_wav2bas01_.bas") + + dest_content = self._get_and_delete_dst(destination_filepath) + + self.assertEqual(dest_content, ( + '10 FOR I = 1 TO 10\n' + '20 PRINT I;"HELLO WORLD!"\n' + '30 NEXT I\n' + )) + + def test_wav2bas02(self): + source_filepath = self._src_file_path("HelloWorld1 origin.wav") + destination_filepath = self._dst_file_path("unittest_wav2bas02.bas") + wav2bas(source_filepath, destination_filepath, self.cfg) + + # no filename used in CSAVE: + destination_filepath = self._dst_file_path("unittest_wav2bas02_.bas") + + dest_content = self._get_and_delete_dst(destination_filepath) + + self.assertEqual(dest_content, ( + '10 FOR I = 1 TO 10\n' + '20 PRINT I;"HELLO WORLD!"\n' + '30 NEXT I\n' + )) + + def test_wav2bas03(self): + source_filepath = self._src_file_path("LineNumber Test 01.wav") + destination_filepath = self._dst_file_path("unittest_wav2bas03.bas") + wav2bas(source_filepath, destination_filepath, self.cfg) + + # filename 'LINENO01' used in CSAVE: + destination_filepath = self._dst_file_path("unittest_wav2bas03_LINENO01.bas") + + dest_content = self._get_and_delete_dst(destination_filepath) + + self.assertEqual(dest_content, ( + '1 PRINT "LINE NUMBER TEST"\n' + '10 PRINT 10\n' + '100 PRINT 100\n' + '1000 PRINT 1000\n' + '10000 PRINT 10000\n' + '32768 PRINT 32768\n' + '63999 PRINT "END";63999\n' + )) + + def test_wav2bas04(self): + source_filepath = self._src_file_path("LineNumber Test 02.wav") + destination_filepath = self._dst_file_path("unittest_wav2bas03.bas") + wav2bas(source_filepath, destination_filepath, self.cfg) + + # filename 'LINENO02' used in CSAVE: + destination_filepath = self._dst_file_path("unittest_wav2bas03_LINENO02.bas") + + dest_content = self._get_and_delete_dst(destination_filepath) + + self.assertEqual(dest_content, ( + '1 PRINT "LINE NUMBER TEST"\n' + '10 PRINT 10\n' + '100 PRINT 100\n' + '1000 PRINT 1000\n' + '10000 PRINT 10000\n' + '32768 PRINT 32768\n' + '63999 PRINT "END";63999\n' + )) + + +if __name__ == '__main__': + log = logging.getLogger("PyDC") + log.setLevel( + logging.ERROR +# logging.WARNING +# logging.DEBUG + ) + log.addHandler(logging.StreamHandler()) + + unittest.main( + argv=( + sys.argv[0], +# "TestDragon32Conversion.test_wav2bas01", +# "TestDragon32Conversion.test_wav2bas04", + ), +# verbosity=1, + verbosity=2, + failfast=True, + ) From d1ccf069a0a273a3893a050fb480eb8f08b01d5b Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Fri, 30 Aug 2013 20:39:42 +0200 Subject: [PATCH 045/151] consume magic byte in bitstream, too. Conflicts: PyDC/PyDC/PyDC.py --- PyDC/PyDC/PyDC.py | 103 ++++++++++--------------------------------- PyDC/PyDC/configs.py | 1 + 2 files changed, 25 insertions(+), 79 deletions(-) diff --git a/PyDC/PyDC/PyDC.py b/PyDC/PyDC/PyDC.py index a17883d5..049c2f8f 100755 --- a/PyDC/PyDC/PyDC.py +++ b/PyDC/PyDC/PyDC.py @@ -104,7 +104,7 @@ def feed(self, bitstream): # bitstream = iter(bitstream) try: - block_type, block_length, codepoints, checksum = self.get_block_info(bitstream) + block_type, block_length, codepoints = self.get_block_info(bitstream) except SyncByteNotFoundError, err: log.error(err) break @@ -224,7 +224,13 @@ def get_block_info(self, bitstream): hex(origin_checksum), hex(calc_checksum) )) - return block_type, block_length, codepoints, origin_checksum + magic_byte = next(codepoint_stream) + if magic_byte != self.cfg.MAGIC_BYTE: + log.error("Magic Byte %s is not %s" % (hex(magic_byte), hex(self.cfg.MAGIC_BYTE))) + else: + log.info("Magic Byte %s, ok." % hex(magic_byte)) + + return block_type, block_length, codepoints @@ -250,6 +256,7 @@ def print_bit_list_stats(bit_list): print "%i positive bits and %i negative bits" % (positive_count, negative_count) + if __name__ == "__main__": import doctest print doctest.testmod( @@ -258,80 +265,18 @@ def print_bit_list_stats(bit_list): ) # sys.exit() - - - # created by Xroar Emulator -# FILENAME = "HelloWorld1 xroar.wav" # 8Bit 22050Hz - - # created by origin Dragon 32 machine -# FILENAME = "HelloWorld1 origin.wav" # 16Bit 44.1KHz mono - - # Test files from: - # http://archive.worldofdragon.org/archive/index.php?dir=Tapes/Dragon/wav/ -# FILENAME = "Quickbeam Software - Duplicas v3.0.wav" # binary! - - - FILENAME = "Dragon Data Ltd - Examples from the Manual - 39~58 [run].wav" - -# FILENAME = "1_MANIA.WAV" # 148579 frames, 4879 bits (raw) -# FILENAME = "2_DBJ.WAV" # TODO - - # BASIC file with high line numbers: -# FILENAME = "LineNumber Test 01.wav" # tokenized BASIC - no sync -# FILENAME = "LineNumber Test 02.wav" # ASCII BASIC - no sync - - - -# log_level = LOG_LEVEL_DICT[3] # args.verbosity -# log.setLevel(logging.DEBUG) - log.setLevel(logging.INFO) -# -# logfilename = FILENAME + ".log" # args.logfile -# if logfilename: -# print "Log into '%s'" % logfilename -# handler = logging.FileHandler(logfilename, mode='w', encoding="utf8") -# handler.setFormatter(LOG_FORMATTER) -# log.addHandler(handler) -# -# # if args.stdout_log: - handler = logging.StreamHandler() - handler.setFormatter(LOG_FORMATTER) - log.addHandler(handler) - - d32cfg = Dragon32Config() - c = Cassette(d32cfg) - -# filepath = os.path.abspath("../test_files/%s" % FILENAME) - filepath = "../test_files/%s" % FILENAME - - # get bitstream from WAVE file: - st = Wave2Bitstream(filepath, - bit_nul_hz=1200, # "0" is a single cycle at 1200 Hz - bit_one_hz=2400, # "1" is a single cycle at 2400 Hz - hz_variation=450, # How much Hz can signal scatter to match 1 or 0 bit ? - min_volume_ratio=5, # percent volume to ignore sample - avg_count=0, # How many samples should be merged into a average value? - end_count=2, # Sample count that must be pos/neg at once - mid_count=1 # Sample count that can be around null - ) - bitstream = iter(st) - - - # Create a bitstream from a existing .bas file: -# c.add_from_bas("test_files/HelloWorld1.bas") -# c.add_from_bas("test_files/Dragon Data Ltd - Examples from the Manual - 39~58 [run].bas") -# c.add_from_bas("test_files/LineNumberTest.bas") -# c.print_debug_info() -# bitstream = c.get_as_bitstream() - - -# bitstream.sync(32) # Sync bitstream to wave sinus cycle -# bitstream = list(bitstream) -# print " ***** Bitstream length:", len(bitstream) -# print_bitlist(bitstream) -# bitstream = iter(bitstream) - - - bh = BitstreamHandler(d32cfg) - bh.feed(bitstream) - + import sys, time, subprocess + subprocess.Popen([sys.executable, "../PyDC_cli.py", "--verbosity=10", + # bas -> wav + "../test_files/HelloWorld1.bas", "../test.wav" + ]) + sys.stderr.flush() + sys.stdout.flush() + print "="*79 + subprocess.Popen([sys.executable, "../PyDC_cli.py", "--verbosity=10", + # wav -> bas + "../test.wav", "../test.bas", +# "../test_files/HelloWorld1 origin.wav", "../test_files/HelloWorld1.bas", + ]) + + print "-- END --" diff --git a/PyDC/PyDC/configs.py b/PyDC/PyDC/configs.py index 9c6cff6c..77452714 100644 --- a/PyDC/PyDC/configs.py +++ b/PyDC/PyDC/configs.py @@ -77,6 +77,7 @@ class Dragon32Config(BaseConfig): LEAD_BYTE_CODEPOINT = 0x55 # 10101010 LEAD_BYTE_LEN = 255 SYNC_BYTE_CODEPOINT = 0x3C # 00111100 + MAGIC_BYTE = 0x55 MAX_SYNC_BYTE_SEARCH = 600 # search size in **Bytes** # Block types: From 332677a25917c77c970c6a41e55101aef341706f Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Tue, 3 Sep 2013 13:52:45 +0200 Subject: [PATCH 046/151] better test output --- PyDC/PyDC/tests.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/PyDC/PyDC/tests.py b/PyDC/PyDC/tests.py index ee5d80a1..0f62319c 100755 --- a/PyDC/PyDC/tests.py +++ b/PyDC/PyDC/tests.py @@ -22,11 +22,16 @@ class TestDragon32Conversion(unittest.TestCase): def setUp(self): + print + print "="*79 self.base_path = os.path.normpath( os.path.join(os.path.split(configs.__file__)[0], "..") ) self.cfg = configs.Dragon32Config() + def tearDown(self): + print "\n"*2 + def _src_file_path(self, filename): return os.path.relpath( os.path.join(self.base_path, "test_files", filename) @@ -120,7 +125,8 @@ def test_wav2bas04(self): if __name__ == '__main__': log = logging.getLogger("PyDC") log.setLevel( - logging.ERROR + #~ logging.ERROR + logging.INFO # logging.WARNING # logging.DEBUG ) From a88259e61f240cc34b2c10ce74635798d9491ee8 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Fri, 30 Aug 2013 21:19:48 +0200 Subject: [PATCH 047/151] code cleanup Conflicts: PyDC/PyDC/CassetteObjects.py PyDC/PyDC/__init__.py PyDC/PyDC/base_cli.py PyDC/PyDC/bitstream_handler.py PyDC/PyDC/wave2bitstream.py --- PyDC/PyDC/CassetteObjects.py | 90 +++++--- PyDC/PyDC/__init__.py | 54 +++-- PyDC/PyDC/base_cli.py | 25 ++- PyDC/PyDC/{PyDC.py => bitstream_handler.py} | 40 ++-- PyDC/PyDC/wave2bitstream.py | 224 +++++++++++++------- PyDC/PyDC_cli.py | 14 +- 6 files changed, 279 insertions(+), 168 deletions(-) rename PyDC/PyDC/{PyDC.py => bitstream_handler.py} (90%) diff --git a/PyDC/PyDC/CassetteObjects.py b/PyDC/PyDC/CassetteObjects.py index 32339912..f6879a0d 100644 --- a/PyDC/PyDC/CassetteObjects.py +++ b/PyDC/PyDC/CassetteObjects.py @@ -303,22 +303,22 @@ def create_from_wave(self, codepoints): # print "file meta:" # print_codepoint_stream(codepoints) - self.file_type = codepoints[9] + self.file_type = codepoints[8] if not self.file_type in self.cfg.FILETYPE_DICT: raise NotImplementedError( "Unknown file type %s is not supported, yet." % hex(self.file_type) ) - print "file type:", self.cfg.FILETYPE_DICT[self.file_type] + log.info("file type: %s" % self.cfg.FILETYPE_DICT[self.file_type]) if self.file_type == self.cfg.FTYPE_DATA: raise NotImplementedError("Data files are not supported, yet.") elif self.file_type == self.cfg.FTYPE_BIN: raise NotImplementedError("Binary files are not supported, yet.") - ascii_flag = codepoints[10] - print "ASCII Flag is:", repr(ascii_flag) + ascii_flag = codepoints[9] + log.info("Raw ASCII flag is: %s" % repr(ascii_flag)) if ascii_flag == self.cfg.BASIC_TOKENIZED: self.is_tokenized = True elif ascii_flag == self.cfg.BASIC_ASCII: @@ -326,7 +326,7 @@ def create_from_wave(self, codepoints): else: raise NotImplementedError("Unknown BASIC type: '%s'" % hex(ascii_flag)) - print "code type is:", self.cfg.BASIC_TYPE_DICT[ascii_flag] + log.info("ASCII flag: %s" % self.cfg.BASIC_TYPE_DICT[ascii_flag]) self.file_content = FileContent(self.cfg) @@ -335,6 +335,7 @@ def add_block_data(self, block_length, codepoints): self.file_content.add_block_data(block_length, codepoints) else: self.file_content.add_ascii_block(block_length, codepoints) + print "*"*79 self.file_content.print_code_lines() print "*"*79 @@ -342,8 +343,13 @@ def add_block_data(self, block_length, codepoints): def get_filename_block_as_codepoints(self): codepoints = [] codepoints += list(string2codepoint(self.filename.ljust(8, " "))) - codepoints.append(self.cfg.FTYPE_BASIC) - codepoints.append(self.cfg.BASIC_ASCII) # ASCII BASIC + codepoints.append(self.cfg.FTYPE_BASIC) # one byte file type + codepoints.append(self.cfg.BASIC_ASCII) # one byte ASCII flag + codepoints.append(0x00) # one byte gap flag (00=no gaps, FF=gaps) + # FIXME, see: http://five.pairlist.net/pipermail/coco/2013-August/070938.html + codepoints += [0x00, 0x00] # two bytes machine code starting address + codepoints += [0x00, 0x00] # two bytes machine code loading address + log.debug("filename block: %s" % pformat_codepoints(codepoints)) return codepoints def get_code_block_as_codepoints(self): @@ -416,7 +422,7 @@ def add_block(self, block_type, block_length, block_codepoints): elif block_type == self.cfg.FILENAME_BLOCK: self.current_file = CassetteFile(self.cfg) self.current_file.create_from_wave(block_codepoints) - print "Add file %s" % repr(self.current_file) + log.info("Add file %s" % repr(self.current_file)) self.files.append(self.current_file) elif block_type == self.cfg.DATA_BLOCK: self.current_file.add_block_data(block_length, block_codepoints) @@ -429,29 +435,50 @@ def print_debug_info(self): file_obj.print_debug_info() def block2codepoint_stream(self, block_type, block_codepoints): - log.debug("%s x LEAD_BYTE_CODEPOINT" % self.cfg.LEAD_BYTE_LEN) + log.debug("yield %sx lead byte %s" % ( + self.cfg.LEAD_BYTE_LEN, hex(self.cfg.LEAD_BYTE_CODEPOINT) + )) for count in xrange(self.cfg.LEAD_BYTE_LEN): yield self.cfg.LEAD_BYTE_CODEPOINT - log.debug("\n1x SYNC_BYTE_CODEPOINT") + + log.debug("yield sync byte %s" % hex(self.cfg.SYNC_BYTE_CODEPOINT)) yield self.cfg.SYNC_BYTE_CODEPOINT - log.debug("\nblock type is: '%s'" % self.cfg.BLOCK_TYPE_DICT[block_type]) + log.debug("yield block type '%s'" % self.cfg.BLOCK_TYPE_DICT[block_type]) yield block_type block_length = len(block_codepoints) - log.debug("\nblock length: %s" % hex(block_length)) + log.debug("yield block length %s (%sBytes)" % (hex(block_length), block_length)) yield block_length - if block_codepoints: # not if EOF block - log.debug("\nyield %s" % self.cfg.BLOCK_TYPE_DICT[block_type]) - print "-"*79 - block_codepoints = list(block_codepoints) - print pformat_codepoints(block_codepoints) - block_codepoints = iter(block_codepoints) - print "-"*79 + if not block_codepoints: + # EOF block + # FIXME checksum + checksum = block_type + checksum += block_length + checksum = checksum & 0xFF + log.debug("yield calculated checksum %s" % hex(checksum)) + yield checksum + log.debug("yield magic byte %s" % hex(self.cfg.MAGIC_BYTE)) + yield self.cfg.MAGIC_BYTE # 0x55 + else: + log.debug("yield '%s':" % self.cfg.BLOCK_TYPE_DICT[block_type]) + log.debug("-"*79) + log.debug(pformat_codepoints(block_codepoints)) + log.debug("-"*79) + checksum = 0x00 for codepoint in block_codepoints: + checksum += codepoint yield codepoint + checksum += block_type + checksum += block_length + checksum = checksum & 0xFF + log.debug("yield calculated checksum %s" % hex(checksum)) + yield checksum + log.debug("yield magic byte %s" % hex(self.cfg.MAGIC_BYTE)) + yield self.cfg.MAGIC_BYTE # 0x55 + def codepoint_stream(self): for file_obj in self.files: # yield filename @@ -518,21 +545,20 @@ def pprint_codepoint_stream(self): # verbose=False # # verbose=True # ) -# sys.exit() - - log_level = LOG_LEVEL_DICT[3] - log.setLevel(log_level) + import time, subprocess + subprocess.Popen([sys.executable, "../PyDC_cli.py", "--verbosity=10", + # bas -> wav + "../test_files/HelloWorld1.bas", "../test.wav" + ]).wait() - handler = logging.StreamHandler(stream=sys.stdout) - handler.setFormatter(LOG_FORMATTER) - log.addHandler(handler) + print "="*79 + subprocess.Popen([sys.executable, "../PyDC_cli.py", "--verbosity=10", + # wav -> bas + "../test.wav", "../test.bas", +# "../test_files/HelloWorld1 origin.wav", "../test_files/HelloWorld1.bas", + ]).wait() - d32cfg = Dragon32Config() - c = Cassette(d32cfg) - c.add_from_bas("../test_files/HelloWorld1.bas") - c.print_debug_info() -# print list(c.codepoint_stream()) - print list(c.get_as_bitstream()) + print "-- END --" diff --git a/PyDC/PyDC/__init__.py b/PyDC/PyDC/__init__.py index da6ec817..d20c1488 100644 --- a/PyDC/PyDC/__init__.py +++ b/PyDC/PyDC/__init__.py @@ -9,8 +9,10 @@ :license: GNU GPL v3 or above, see LICENSE for more details. """ -from wave2bitstream import Wave2Bitstream -from PyDC import BitstreamHandler +from CassetteObjects import Cassette +from bitstream_handler import BitstreamHandler +from utils import print_bitlist +from wave2bitstream import Wave2Bitstream, Bitstream2Wave __version__ = (0, 1, 0, 'dev') @@ -19,18 +21,26 @@ TITLE_LINE = "PyDC v%s copyleft 2013 by htfx.de - Jens Diemer, GNU GPL v3 or above" % VERSION_STRING -def bas2wav(self): - raise NotImplementedError("TBD") - # Create a bitstream from a existing .bas file: -# c.add_from_bas("test_files/HelloWorld1.bas") -# c.add_from_bas("test_files/Dragon Data Ltd - Examples from the Manual - 39~58 [run].bas") -# c.add_from_bas("test_files/LineNumberTest.bas") -# c.print_debug_info() -# bitstream = c.get_as_bitstream() +def bas2wav(source_filepath, destination_filepath, cfg): + """ + Create a bitstream from a existing .bas file + """ + + c = Cassette(cfg) + + c.add_from_bas(source_filepath) + c.print_debug_info() + bitstream = c.get_as_bitstream() +# print_bitlist(bitstream) + + bw = Bitstream2Wave(bitstream, cfg) + bw.write_wave(destination_filepath) def wav2bas(source_filepath, destination_filepath, cfg): - # get bitstream generator from WAVE file: + """ + get bitstream generator from WAVE file + """ bitstream = iter(Wave2Bitstream(source_filepath, cfg)) # store bitstream into python objects @@ -50,12 +60,18 @@ def wav2bas(source_filepath, destination_filepath, cfg): # ) print TITLE_LINE - from configs import Dragon32Config - cfg = Dragon32Config() - wav2bas( - "../test_files/HelloWorld1 origin.wav", - "../HelloWorld1 origin.bas", - cfg - ) + # test via CLI: + + import sys, subprocess + subprocess.Popen([sys.executable, "../PyDC_cli.py", "--verbosity=10", + # bas -> wav + "../test_files/HelloWorld1.bas", "../test.wav" + ]).wait() + + subprocess.Popen([sys.executable, "../PyDC_cli.py", "--verbosity=10", + # wav -> bas +# "../test.wav", "../test.bas", + "../test_files/HelloWorld1 origin.wav", "../test_files/HelloWorld1.bas", + ]).wait() - print "\n --- END ---" + print "-- END --" diff --git a/PyDC/PyDC/base_cli.py b/PyDC/PyDC/base_cli.py index 5ebcb21d..4b6abd19 100644 --- a/PyDC/PyDC/base_cli.py +++ b/PyDC/PyDC/base_cli.py @@ -11,12 +11,11 @@ import argparse import logging -import os -import sys def get_log_levels(): - levels = [level for level in logging._levelNames if isinstance(level, int)] + levels = [5] # FIXME + levels += [level for level in logging._levelNames if isinstance(level, int)] return levels LOG_LEVELS = get_log_levels() @@ -27,7 +26,7 @@ class Base_CLI(object): DESCRIPTION = None EPOLOG = None VERSION = None - LOG_FORMATTER = logging.Formatter("%(asctime)s %(message)s") + DEFAULT_LOG_FORMATTER = "%(message)s" def __init__(self): self.logfilename = None @@ -48,21 +47,27 @@ def __init__(self): "--verbosity", type=int, choices=LOG_LEVELS, default=logging.WARNING, help=( "verbosity level to stdout (lower == more output!)" - " (default: %s)" % logging.WARNING + " (default: %s)" % logging.INFO ) ) self.parser.add_argument( "--logfile", type=int, choices=LOG_LEVELS, default=logging.INFO, help=( "verbosity level to log file (lower == more output!)" - " (default: %s)" % logging.INFO + " (default: %s)" % logging.DEBUG + ) + ) + self.parser.add_argument( + "--log_format", default=self.DEFAULT_LOG_FORMATTER, + help=( + "see: http://docs.python.org/2/library/logging.html#logrecord-attributes" ) ) def parse_args(self): if self.DESCRIPTION is not None: print - print self.DESCRIPTION + print self.DESCRIPTION, self.VERSION print "-"*79 print @@ -99,9 +104,15 @@ def setup_logging(self, args): handler.setFormatter(self.LOG_FORMATTER) self.log.addHandler(handler) + self.log.debug(" ".join(sys.argv)) + + verbosity_level_name = logging.getLevelName(self.verbosity) self.log.info("Verbosity log level: %s" % verbosity_level_name) + + logfile_level_name = logging.getLevelName(self.logfile) self.log.info("logfile log level: %s" % logfile_level_name) + if __name__ == "__main__": import doctest print doctest.testmod( diff --git a/PyDC/PyDC/PyDC.py b/PyDC/PyDC/bitstream_handler.py similarity index 90% rename from PyDC/PyDC/PyDC.py rename to PyDC/PyDC/bitstream_handler.py index 049c2f8f..5a0c2511 100755 --- a/PyDC/PyDC/PyDC.py +++ b/PyDC/PyDC/bitstream_handler.py @@ -123,7 +123,7 @@ def feed(self, bitstream): print "*** block type: 0x%x (%s)" % (block_type, block_type_name) if block_type == self.cfg.EOF_BLOCK: - print "EOF-Block found" + log.info("EOF-Block found") break if block_length == 0: @@ -224,6 +224,8 @@ def get_block_info(self, bitstream): hex(origin_checksum), hex(calc_checksum) )) + # Check if the magic byte exists + magic_byte = next(codepoint_stream) if magic_byte != self.cfg.MAGIC_BYTE: log.error("Magic Byte %s is not %s" % (hex(magic_byte), hex(self.cfg.MAGIC_BYTE))) @@ -258,25 +260,27 @@ def print_bit_list_stats(bit_list): if __name__ == "__main__": - import doctest - print doctest.testmod( - verbose=False - # verbose=True - ) -# sys.exit() - - import sys, time, subprocess +# import doctest +# print doctest.testmod( +# verbose=False +# # verbose=True +# ) + + # test via CLI: + + import sys, subprocess + + # bas -> wav subprocess.Popen([sys.executable, "../PyDC_cli.py", "--verbosity=10", - # bas -> wav +# "--log_format=%(module)s %(lineno)d: %(message)s", "../test_files/HelloWorld1.bas", "../test.wav" - ]) - sys.stderr.flush() - sys.stdout.flush() - print "="*79 + ]).wait() + + # wav -> bas subprocess.Popen([sys.executable, "../PyDC_cli.py", "--verbosity=10", - # wav -> bas - "../test.wav", "../test.bas", -# "../test_files/HelloWorld1 origin.wav", "../test_files/HelloWorld1.bas", - ]) +# "--log_format=%(module)s %(lineno)d: %(message)s", +# "../test.wav", "../test.bas", + "../test_files/HelloWorld1 origin.wav", "../test_files/HelloWorld1.bas", + ]).wait() print "-- END --" diff --git a/PyDC/PyDC/wave2bitstream.py b/PyDC/PyDC/wave2bitstream.py index f33f2f20..be945614 100644 --- a/PyDC/PyDC/wave2bitstream.py +++ b/PyDC/PyDC/wave2bitstream.py @@ -14,6 +14,8 @@ import logging import struct import time +import math + try: import audioop @@ -24,31 +26,46 @@ # own modules from utils import average, diff_info, TextLevelMeter, iter_window, \ - human_duration, ProcessInfo, LOG_LEVEL_DICT, LOG_FORMATTER, print_bitlist, \ - count_sign, iter_steps -from configs import Dragon32Config - + human_duration, ProcessInfo, print_bitlist, \ + count_sign, iter_steps, sinus_values_by_hz log = logging.getLogger("PyDC") +WAVE_READ_SIZE = 16 * 1024 # How many frames should be read from WAVE file at once? +WAV_ARRAY_TYPECODE = { + 1: "b", # 8-bit wave file + 2: "h", # 16-bit wave file + 4: "l", # 32-bit wave file TODO: Test it +} + +# Maximum volume value in wave files: +MAX_VALUES = { + 1: 255, # 8-bit wave file + 2: 32768, # 16-bit wave file + 4: 2147483647, # 32-bit wave file +} +HUMAN_SAMPLEWIDTH = { + 1: "8-bit", + 2: "16-bit", + 4: "32-bit", +} + + +class WaveBase(object): + def get_typecode(self, samplewidth): + try: + typecode = WAV_ARRAY_TYPECODE[samplewidth] + except KeyError: + raise NotImplementedError( + "Only %s wave files are supported, yet!" % ( + ", ".join(["%sBit" % (i * 8) for i in WAV_ARRAY_TYPECODE.keys()]) + ) + ) + return typecode -class Wave2Bitstream(object): - - WAVE_READ_SIZE = 16 * 1024 # How many frames should be read from WAVE file at once? - WAV_ARRAY_TYPECODE = { - 1: "b", # 8-bit wave file - 2: "h", # 16-bit wave file - 4: "l", # 32-bit wave file TODO: Test it - } - - # Maximum volume value in wave files: - MAX_VALUES = { - 1: 255, # 8-bit wave file - 2: 32768, # 16-bit wave file - 4: 2147483647, # 32-bit wave file - } +class Wave2Bitstream(WaveBase): def __init__(self, wave_filename, cfg): self.wave_filename = wave_filename @@ -77,7 +94,7 @@ def __init__(self, wave_filename, cfg): self.samplewidth = self.wavefile.getsampwidth() # 1 for 8-bit, 2 for 16-bit, 4 for 32-bit samples print "samplewidth: %i (%sBit wave file)" % (self.samplewidth, self.samplewidth * 8) - self.max_value = self.MAX_VALUES[self.samplewidth] + self.max_value = MAX_VALUES[self.samplewidth] print "the max volume value is:", self.max_value self.min_volume = int(round(self.max_value * cfg.MIN_VOLUME_RATIO / 100)) @@ -207,7 +224,7 @@ def _print_status(frame_no, bit_count): hz = self.framerate / duration if hz > self.bit_one_min_hz and hz < self.bit_one_max_hz: - log.debug( + log.log(5, "bit 1 at %s in %sSamples = %sHz" % (frame_no, duration, hz) ) bit_count += 1 @@ -219,7 +236,7 @@ def _print_status(frame_no, bit_count): one_hz_max = hz one_hz_avg = average(one_hz_avg, hz, one_hz_count) elif hz > self.bit_nul_min_hz and hz < self.bit_nul_max_hz: - log.debug( + log.log(5, "bit 0 at %s in %sSamples = %sHz" % (frame_no, duration, hz) ) bit_count += 1 @@ -231,7 +248,7 @@ def _print_status(frame_no, bit_count): nul_hz_max = hz nul_hz_avg = average(nul_hz_avg, hz, nul_hz_count) else: - log.debug( + log.log(5, "Skip signal at %s with %sSamples = %sHz" % (frame_no, duration, hz) ) continue @@ -307,13 +324,13 @@ def iter_trigger(self, iter_wave_values): ] # yield the mid crossing if in_pos == False and sign_info == pos_null_transit: - log.debug("sinus curve goes from negative into positive") + log.log(5,"sinus curve goes from negative into positive") # log.debug(" %s | %s | %s" % (previous_values, mid_values, next_values)) yield mid_values[mid_index][0] in_pos = True elif in_pos == True and sign_info == neg_null_transit: if self.half_sinus: - log.debug("sinus curve goes from positive into negative") + log.log(5,"sinus curve goes from positive into negative") # log.debug(" %s | %s | %s" % (previous_values, mid_values, next_values)) yield mid_values[mid_index][0] in_pos = False @@ -336,29 +353,28 @@ def iter_avg_wave_values(self, wave_values_generator): print "\nError getting %i from; %s: %s" % (mid_index, repr(value_tuples), err) frame_no = value_tuples[0][0] result = (frame_no, values) -# if log.level >= logging.DEBUG: -# log.debug("average %s samples to: %s" % (repr(value_tuples), repr(result))) + if log.level >= 5: + log.log(5, "average %s samples to: %s" % (repr(value_tuples), repr(result))) # print "average %s samples to: %s" % (repr(value_tuples), repr(result)) yield result - def iter_wave_values(self): """ yield frame numer + volume value from the WAVE file """ - try: - typecode = self.WAV_ARRAY_TYPECODE[self.samplewidth] - except KeyError: - raise NotImplementedError( - "Only %s wave files are supported, yet!" % ( - ", ".join(["%sBit" % (i * 8) for i in self.WAV_ARRAY_TYPECODE.keys()]) - ) - ) + typecode = self.get_typecode(self.samplewidth) tlm = TextLevelMeter(self.max_value, 79) + # Use only a read size which is a quare divider of the samplewidth + # Otherwise array.array will raise: ValueError: string length not a multiple of item size + divider = int(round(float(WAVE_READ_SIZE) / self.samplewidth)) + read_size = self.samplewidth * divider + if read_size != WAVE_READ_SIZE: + log.info("Real use wave read size: %i Bytes" % read_size) + self.frame_no = 0 - get_wave_block_func = functools.partial(self.wavefile.readframes, self.WAVE_READ_SIZE) + get_wave_block_func = functools.partial(self.wavefile.readframes, read_size) skip_count = 0 manually_audioop_bias = self.samplewidth == 1 and audioop is None @@ -373,7 +389,23 @@ def iter_wave_values(self): # http://docs.python.org/2/library/audioop.html#audioop.lin2lin frames = audioop.bias(frames, 1, 128) - for value in array.array(typecode, frames): + try: + values = array.array(typecode, frames) + except ValueError, err: + # e.g.: + # ValueError: string length not a multiple of item size + # Work-a-round: Skip the last frames of this block + frame_count = len(frames) + divider = int(math.floor(float(frame_count) / self.samplewidth)) + new_count = self.samplewidth * divider + frames = frames[:new_count] # skip frames + log.error( + "Can't make array from %s frames: Value error: %s (Skip %i and use %i frames)" % ( + frame_count, err, frame_count - new_count, len(frames) + )) + values = array.array(typecode, frames) + + for value in values: if manually_audioop_bias: # audioop.bias can't be used. @@ -385,13 +417,11 @@ def iter_wave_values(self): skip_count += 1 continue - msg = tlm.feed(value) - if log.level >= logging.DEBUG: - # ~ try: + + if log.level >= 5: + msg = tlm.feed(value) percent = 100.0 / self.max_value * abs(value) - # ~ except - - log.debug( + log.log(5, "%s value: %i (%.1f%%)" % (msg, value, percent) ) @@ -404,48 +434,82 @@ def iter_wave_values(self): skip_count, self.min_volume )) + +class Bitstream2Wave(WaveBase): + def __init__(self, bitstream, cfg): + self.bitstream = bitstream + self.cfg = cfg + + wave_max_value = MAX_VALUES[self.cfg.SAMPLEWIDTH] + self.used_max_values = int(round( + float(wave_max_value) / 100 * self.cfg.VOLUME_RATIO + )) + log.info("Create %s wave file with %sHz and %s max volumen (%s%%)" % ( + HUMAN_SAMPLEWIDTH[self.cfg.SAMPLEWIDTH], + self.cfg.FRAMERATE, + self.used_max_values, self.cfg.VOLUME_RATIO + )) + + typecode = self.get_typecode(self.cfg.SAMPLEWIDTH) + + self.bit_nul_samples = self.get_samples(typecode, self.cfg.BIT_NUL_HZ) + self.bit_one_samples = self.get_samples(typecode, self.cfg.BIT_ONE_HZ) + + def get_samples(self, typecode, hz): + values = tuple( + sinus_values_by_hz(self.cfg.FRAMERATE, hz, self.used_max_values) + ) + real_hz = float(self.cfg.FRAMERATE) / len(values) + log.debug("Real frequency: %.2f" % real_hz) + return array.array(typecode, values) + + def write_wave(self, destination_filepath): + print "create wave file '%s'..." % destination_filepath + try: + wavefile = wave.open(destination_filepath, "wb") + except IOError, err: + log.error("Error opening %s: %s" % (repr(destination_filepath), err)) + sys.exit(-1) + + wavefile.setnchannels(1) # Mono + wavefile.setsampwidth(self.cfg.SAMPLEWIDTH) + wavefile.setframerate(self.cfg.FRAMERATE) + + for bit in self.bitstream: + if bit == 0: +# wavefile.writeframes(self.bit_nul_samples) + wavefile.writeframesraw(self.bit_nul_samples) + elif bit == 1: +# wavefile.writeframes(self.bit_one_samples) + wavefile.writeframesraw(self.bit_one_samples) + else: + raise TypeError + + wavefile.close() + + if __name__ == "__main__": import doctest print doctest.testmod( verbose=False # verbose=True ) -# sys.exit() - - FILENAME = "HelloWorld1 xroar.wav" # 8Bit 22050Hz -# FILENAME = "HelloWorld1 origin.wav" # 109922 frames, 16Bit wave, 44100Hz - # ~ FILENAME = "LineNumber Test 01.wav" # tokenized BASIC - -# log_level = LOG_LEVEL_DICT[3] # args.verbosity -# log.setLevel(logging.DEBUG) - log.setLevel(logging.INFO) -# -# logfilename = FILENAME + ".log" # args.logfile -# if logfilename: -# print "Log into '%s'" % logfilename -# handler = logging.FileHandler(logfilename, -# # mode='a', -# mode='w', -# encoding="utf8" -# ) -# handler.setFormatter(LOG_FORMATTER) -# log.addHandler(handler) - - # if args.stdout_log: - handler = logging.StreamHandler() - handler.setFormatter(LOG_FORMATTER) - log.addHandler(handler) - - d32cfg = Dragon32Config() - - st = Wave2Bitstream("../test_files/%s" % FILENAME, d32cfg) - - bitstream = iter(st) - bitstream.sync(32) - - print "-"*79 - print_bitlist(bitstream, no_repr=True) - print "-"*79 + # test via CLI: + + import sys, subprocess + + # bas -> wav + subprocess.Popen([sys.executable, "../PyDC_cli.py", "--verbosity=10", +# "--log_format=%(module)s %(lineno)d: %(message)s", + "../test_files/HelloWorld1.bas", "../test.wav" + ]).wait() + + # wav -> bas + subprocess.Popen([sys.executable, "../PyDC_cli.py", "--verbosity=10", +# "--log_format=%(module)s %(lineno)d: %(message)s", +# "../test.wav", "../test.bas", + "../test_files/HelloWorld1 origin.wav", "../test_files/HelloWorld1.bas", + ]).wait() print "-- END --" diff --git a/PyDC/PyDC_cli.py b/PyDC/PyDC_cli.py index ef80bb65..dcdca9e2 100755 --- a/PyDC/PyDC_cli.py +++ b/PyDC/PyDC_cli.py @@ -71,6 +71,8 @@ def __init__(self): def parse_args(self): args = super(PyDC_CLI, self).parse_args() + self.setup_logging(args) + self.source_file = args.src print "source file.......: %s" % self.source_file @@ -89,7 +91,6 @@ def run(self): dest_ext = dest_ext.lower() self.logfilename = dest_filename + ".log" - self.setup_logging(self.args) self.cfg = Dragon32Config() @@ -110,17 +111,6 @@ def run(self): if __name__ == "__main__": -# import doctest -# print doctest.testmod( -# verbose=False -# # verbose=True -# ) - -# sys.argv.append("--help") - -# sys.argv.append("test_files/HelloWorld1 origin.wav") -# sys.argv.append("HelloWorld1 origin.bas") - cli = PyDC_CLI() cli.run() From 0976eda3f92a62b23ed1e720705c25dcd83f42aa Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Fri, 30 Aug 2013 19:27:21 +0200 Subject: [PATCH 048/151] implement bas2wave ;) But seems the stream is not valid, yet :( Conflicts: PyDC/PyDC/CassetteObjects.py PyDC/PyDC/__init__.py PyDC/PyDC/base_cli.py PyDC/PyDC/bitstream_handler.py PyDC/PyDC/configs.py PyDC/PyDC/wave2bitstream.py --- PyDC/PyDC/CassetteObjects.py | 3 + PyDC/PyDC/base_cli.py | 2 + PyDC/PyDC/bitstream_handler.py | 14 ++-- PyDC/PyDC/configs.py | 13 ++- PyDC/PyDC/utils.py | 149 +++++++++++++++++++++++---------- PyDC/PyDC/wave2bitstream.py | 9 +- PyDC/PyDC_cli.py | 6 +- 7 files changed, 136 insertions(+), 60 deletions(-) diff --git a/PyDC/PyDC/CassetteObjects.py b/PyDC/PyDC/CassetteObjects.py index f6879a0d..5e4df9ba 100644 --- a/PyDC/PyDC/CassetteObjects.py +++ b/PyDC/PyDC/CassetteObjects.py @@ -214,6 +214,8 @@ def add_ascii_block(self, block_length, data): 10 PRINT "TEST" 20 PRINT "HELLO WORLD!" """ + data = iter(data) + data.next() # Skip first \r byte_count = 1 # incl. first \r while True: @@ -545,6 +547,7 @@ def pprint_codepoint_stream(self): # verbose=False # # verbose=True # ) +# sys.exit() import time, subprocess subprocess.Popen([sys.executable, "../PyDC_cli.py", "--verbosity=10", diff --git a/PyDC/PyDC/base_cli.py b/PyDC/PyDC/base_cli.py index 4b6abd19..ef6a957a 100644 --- a/PyDC/PyDC/base_cli.py +++ b/PyDC/PyDC/base_cli.py @@ -11,6 +11,8 @@ import argparse import logging +import os +import sys def get_log_levels(): diff --git a/PyDC/PyDC/bitstream_handler.py b/PyDC/PyDC/bitstream_handler.py index 5a0c2511..34de2ab1 100755 --- a/PyDC/PyDC/bitstream_handler.py +++ b/PyDC/PyDC/bitstream_handler.py @@ -258,17 +258,17 @@ def print_bit_list_stats(bit_list): print "%i positive bits and %i negative bits" % (positive_count, negative_count) - if __name__ == "__main__": -# import doctest -# print doctest.testmod( -# verbose=False -# # verbose=True -# ) + import doctest + print doctest.testmod( + verbose=False + # verbose=True + ) +# sys.exit() # test via CLI: - import sys, subprocess + import subprocess # bas -> wav subprocess.Popen([sys.executable, "../PyDC_cli.py", "--verbosity=10", diff --git a/PyDC/PyDC/configs.py b/PyDC/PyDC/configs.py index 77452714..53a3da21 100644 --- a/PyDC/PyDC/configs.py +++ b/PyDC/PyDC/configs.py @@ -13,6 +13,13 @@ class BaseConfig(object): + """ shared config values """ + + # For writing WAVE files: + FRAMERATE = 22050 + SAMPLEWIDTH = 2 # 1 for 8-bit, 2 for 16-bit, 4 for 32-bit samples + VOLUME_RATIO = 90 # "Loundness" in percent of the created wave file + def print_debug_info(self): from utils import byte2bit_string @@ -35,6 +42,8 @@ def print_debug_info(self): class Dragon32Config(BaseConfig): """ + Dragon 32 specific config values + >>> d32cfg = Dragon32Config() >>> d32cfg.print_debug_info() # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS Config: 'Dragon32Config' @@ -74,12 +83,14 @@ class Dragon32Config(BaseConfig): END_COUNT = 2 # Sample count that must be pos/neg at once MID_COUNT = 1 # Sample count that can be around null + # Format values: LEAD_BYTE_CODEPOINT = 0x55 # 10101010 LEAD_BYTE_LEN = 255 SYNC_BYTE_CODEPOINT = 0x3C # 00111100 - MAGIC_BYTE = 0x55 MAX_SYNC_BYTE_SEARCH = 600 # search size in **Bytes** + MAGIC_BYTE = 0x55 # 10101010 + # Block types: FILENAME_BLOCK = 0x00 DATA_BLOCK = 0x01 diff --git a/PyDC/PyDC/utils.py b/PyDC/PyDC/utils.py index f53a153f..7bf6d8fd 100755 --- a/PyDC/PyDC/utils.py +++ b/PyDC/PyDC/utils.py @@ -13,6 +13,7 @@ import logging import types import string +import math LOG_FORMATTER = logging.Formatter("") # %(asctime)s %(message)s") @@ -285,34 +286,34 @@ def get_diff(l): return diff1, diff2 -def iter_pare_sum(data): - """ - >>> def g(data): - ... for no, i in enumerate(data): yield (no, i) - - >>> l = [5,5,10,10,5,5,10,10,5,5,10,10,10,10,5,5,5,5] - >>> len(l) - 18 - >>> list(iter_pare_sum(g(l))) - [(2, 20), (4, 10), (6, 20), (8, 10), (10, 20), (12, 20), (14, 10), (16, 10)] - - - >>> l = [5,10,10,5,5,10,10,5,5,10,10,10,10,5,5,5,5] - >>> len(l) - 17 - >>> list(iter_pare_sum(g(l))) - [(2, 20), (4, 10), (6, 20), (8, 10), (10, 20), (12, 20), (14, 10), (16, 10)] - [(2, 20), (4, 10), (6, 20), (8, 10), (10, 20), (12, 20), (14, 10)] - """ - for previous, current, next_value in itertools.islice(iter_window(data, window_size=3), 0, None, 2): - # ~ print previous, current, next_value - diff1 = abs(previous[1] - current[1]) - diff2 = abs(current[1] - next_value[1]) - - if diff1 < diff2: - yield (current[0], previous[1] + current[1]) - else: - yield (current[0], current[1] + next_value[1]) +# def iter_pare_sum(data): +# """ +# >>> def g(data): +# ... for no, i in enumerate(data): yield (no, i) +# +# >>> l = [5,5,10,10,5,5,10,10,5,5,10,10,10,10,5,5,5,5] +# >>> len(l) +# 18 +# >>> list(iter_pare_sum(g(l))) +# [(2, 20), (4, 10), (6, 20), (8, 10), (10, 20), (12, 20), (14, 10), (16, 10)] +# +# +# >>> l = [5,10,10,5,5,10,10,5,5,10,10,10,10,5,5,5,5] +# >>> len(l) +# 17 +# >>> list(iter_pare_sum(g(l))) +# [(2, 20), (4, 10), (6, 20), (8, 10), (10, 20), (12, 20), (14, 10), (16, 10)] +# [(2, 20), (4, 10), (6, 20), (8, 10), (10, 20), (12, 20), (14, 10)] +# """ +# for previous, current, next_value in itertools.islice(iter_window(data, window_size=3), 0, None, 2): +# # print previous, current, next_value +# diff1 = abs(previous[1] - current[1]) +# diff2 = abs(current[1] - next_value[1]) +# +# if diff1 < diff2: +# yield (current[0], previous[1] + current[1]) +# else: +# yield (current[0], current[1] + next_value[1]) class TextLevelMeter(object): @@ -562,7 +563,7 @@ def pformat_codepoints(codepoints): """ >>> l = pformat_codepoints([13, 70, 111, 111, 32, 66, 97, 114, 32, 33, 13]) >>> repr(l) - "['\\r', 'Foo Bar !', '\\r']" + "['\\\\r', 'Foo Bar !', '\\\\r']" """ printable = string.printable.replace("\n", "").replace("\r", "") line = [] @@ -667,6 +668,7 @@ def get_word(byte_iterator): raise TypeError("Can't build word from %s: %s" % (repr(byte_values), err)) return word + def codepoints2string(codepoints): """ >>> codepoints = [ord(c) for c in "Foo Bar !"] @@ -678,24 +680,81 @@ def codepoints2string(codepoints): return "".join([chr(c) for c in codepoints]) -if __name__ == "__main__": -# sys.exit() +def sinus_values(count, max_value): + """ + >>> values = list(sinus_values(10, 32768)) + >>> len(values) + 10 + >>> values + [0, 21063, 32270, 28378, 11207, -11207, -28378, -32270, -21063, 0] + + >>> tl = TextLevelMeter(32768, width=40) + >>> for v in values: + ... tl.feed(v) + '| * |' + '| | * |' + '| | *|' + '| | * |' + '| | * |' + '| * | |' + '| * | |' + '|* | |' + '| * | |' + '| * |' + """ + count -= 1 + for index in xrange(0, count + 1): + angle = 360.0 / count * index + y = math.sin(math.radians(angle)) * max_value + y = int(round(y)) + yield y + +def sinus_values_by_hz(framerate, hz, max_value): + """ + Create sinus values with the given framerate and Hz. + Note: + We skip the first zero-crossing, so the values can be used directy in a loop. + + >>> values = sinus_values_by_hz(22050, 1200, 255) + >>> len(values) # 22050 / 1200Hz = 18,375 + 18 + >>> values + (87, 164, 221, 251, 251, 221, 164, 87, 0, -87, -164, -221, -251, -251, -221, -164, -87, 0) + + >>> tl = TextLevelMeter(255, width=40) + >>> for v in values: + ... tl.feed(v) + '| | * |' + '| | * |' + '| | * |' + '| | *|' + '| | *|' + '| | * |' + '| | * |' + '| | * |' + '| * |' + '| * | |' + '| * | |' + '| * | |' + '|* | |' + '|* | |' + '| * | |' + '| * | |' + '| * | |' + '| * |' + + >>> values = sinus_values_by_hz(44100, 1200, 255) + >>> len(values) # 44100 / 1200Hz = 36,75 + 37 + """ + count = int(round(float(framerate) / float(hz))) + count += 1 + values = tuple(sinus_values(count, max_value)) + values = values[1:] + return values +if __name__ == "__main__": import doctest print doctest.testmod() - - # ~ import math - - # ~ count = 32 - # ~ max_value = 255 - # ~ width = 79 - - # ~ tl = TextLevelMeter(max_value, width) - # ~ for index in xrange(0, count + 1): - # ~ angle = 360.0 / count * index - # ~ y = math.sin(math.radians(angle)) * max_value - # ~ y = round(y) - # ~ print tl.feed(y) - # ~ # print "%i - %.1f� %i" % (index, angle, y) diff --git a/PyDC/PyDC/wave2bitstream.py b/PyDC/PyDC/wave2bitstream.py index be945614..22d076f9 100644 --- a/PyDC/PyDC/wave2bitstream.py +++ b/PyDC/PyDC/wave2bitstream.py @@ -66,7 +66,6 @@ def get_typecode(self, samplewidth): class Wave2Bitstream(WaveBase): - def __init__(self, wave_filename, cfg): self.wave_filename = wave_filename self.cfg = cfg @@ -324,13 +323,13 @@ def iter_trigger(self, iter_wave_values): ] # yield the mid crossing if in_pos == False and sign_info == pos_null_transit: - log.log(5,"sinus curve goes from negative into positive") + log.log(5, "sinus curve goes from negative into positive") # log.debug(" %s | %s | %s" % (previous_values, mid_values, next_values)) yield mid_values[mid_index][0] in_pos = True elif in_pos == True and sign_info == neg_null_transit: if self.half_sinus: - log.log(5,"sinus curve goes from positive into negative") + log.log(5, "sinus curve goes from positive into negative") # log.debug(" %s | %s | %s" % (previous_values, mid_values, next_values)) yield mid_values[mid_index][0] in_pos = False @@ -358,6 +357,7 @@ def iter_avg_wave_values(self, wave_values_generator): # print "average %s samples to: %s" % (repr(value_tuples), repr(result)) yield result + def iter_wave_values(self): """ yield frame numer + volume value from the WAVE file @@ -417,7 +417,7 @@ def iter_wave_values(self): skip_count += 1 continue - + if log.level >= 5: msg = tlm.feed(value) percent = 100.0 / self.max_value * abs(value) @@ -494,6 +494,7 @@ def write_wave(self, destination_filepath): verbose=False # verbose=True ) +# sys.exit() # test via CLI: diff --git a/PyDC/PyDC_cli.py b/PyDC/PyDC_cli.py index dcdca9e2..4e39b42a 100755 --- a/PyDC/PyDC_cli.py +++ b/PyDC/PyDC_cli.py @@ -13,9 +13,9 @@ import os import sys -from PyDC import TITLE_LINE, VERSION_STRING, wav2bas -from PyDC.configs import Dragon32Config +from PyDC import TITLE_LINE, VERSION_STRING, wav2bas, bas2wav from PyDC.base_cli import Base_CLI +from PyDC.configs import Dragon32Config log = logging.getLogger("PyDC") @@ -103,7 +103,7 @@ def run(self): if source_ext.startswith(".wav") and dest_ext.startswith(".bas"): wav2bas(self.source_file, self.destination_file, self.cfg) elif source_ext.startswith(".bas") and dest_ext.startswith(".wav"): - raise NotImplementedError("TBD") + bas2wav(self.source_file, self.destination_file, self.cfg) else: print "ERROR:" print "%s to %s ???" % (repr(self.source_file), repr(self.destination_file)) From 2ed227ae718f7f6a26a1fc1eb3731cd2ce194593 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Fri, 30 Aug 2013 21:46:41 +0200 Subject: [PATCH 049/151] display absolute wave position Conflicts: PyDC/PyDC/wave2bitstream.py --- PyDC/PyDC/utils.py | 6 ++++++ PyDC/PyDC/wave2bitstream.py | 27 +++++++++++++++------------ 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/PyDC/PyDC/utils.py b/PyDC/PyDC/utils.py index 7bf6d8fd..4fef02e3 100755 --- a/PyDC/PyDC/utils.py +++ b/PyDC/PyDC/utils.py @@ -76,6 +76,12 @@ def human_duration(t): return u"%(number).1f %(type)s" % {'number': count, 'type': name} +def pformat_frame_no(frame_no, framerate): + sec = float(frame_no) / framerate + return "%s (frame no.: %s)" % (human_duration(sec), frame_no) + + + class ProcessInfo(object): """ >>> p = ProcessInfo(100) diff --git a/PyDC/PyDC/wave2bitstream.py b/PyDC/PyDC/wave2bitstream.py index 22d076f9..3a7c4d49 100644 --- a/PyDC/PyDC/wave2bitstream.py +++ b/PyDC/PyDC/wave2bitstream.py @@ -24,11 +24,13 @@ print "Can't use audioop:", err audioop = None + # own modules from utils import average, diff_info, TextLevelMeter, iter_window, \ - human_duration, ProcessInfo, print_bitlist, \ + human_duration, ProcessInfo, pformat_frame_no, \ count_sign, iter_steps, sinus_values_by_hz + log = logging.getLogger("PyDC") @@ -66,10 +68,14 @@ def get_typecode(self, samplewidth): class Wave2Bitstream(WaveBase): + def __init__(self, wave_filename, cfg): self.wave_filename = wave_filename self.cfg = cfg + self.half_sinus = False # in trigger yield the full cycle + self.wave_pos = 0 # Absolute position in the frame stream + assert cfg.END_COUNT > 0 # Sample count that must be pos/neg at once assert cfg.MID_COUNT > 0 # Sample count that can be around null @@ -151,7 +157,7 @@ def sync(self, length): print "Error: no bits identified!" sys.exit(-1) - log.info("First bit is at: %s" % self.frame_no) + log.info("First bit is at: %s" % pformat_frame_no(self.wave_pos, self.framerate)) log.debug("enable half sinus scan") self.half_sinus = True @@ -323,13 +329,13 @@ def iter_trigger(self, iter_wave_values): ] # yield the mid crossing if in_pos == False and sign_info == pos_null_transit: - log.log(5, "sinus curve goes from negative into positive") + log.log(5,"sinus curve goes from negative into positive") # log.debug(" %s | %s | %s" % (previous_values, mid_values, next_values)) yield mid_values[mid_index][0] in_pos = True elif in_pos == True and sign_info == neg_null_transit: if self.half_sinus: - log.log(5, "sinus curve goes from positive into negative") + log.log(5,"sinus curve goes from positive into negative") # log.debug(" %s | %s | %s" % (previous_values, mid_values, next_values)) yield mid_values[mid_index][0] in_pos = False @@ -357,7 +363,6 @@ def iter_avg_wave_values(self, wave_values_generator): # print "average %s samples to: %s" % (repr(value_tuples), repr(result)) yield result - def iter_wave_values(self): """ yield frame numer + volume value from the WAVE file @@ -406,6 +411,7 @@ def iter_wave_values(self): values = array.array(typecode, frames) for value in values: + self.wave_pos += 1 # Absolute position in the frame stream if manually_audioop_bias: # audioop.bias can't be used. @@ -417,7 +423,6 @@ def iter_wave_values(self): skip_count += 1 continue - if log.level >= 5: msg = tlm.feed(value) percent = 100.0 / self.max_value * abs(value) @@ -425,14 +430,12 @@ def iter_wave_values(self): "%s value: %i (%.1f%%)" % (msg, value, percent) ) - self.frame_no += 1 -# if self.frame_no > 100:sys.exit() -# if self.frame_no > 4000:break - yield self.frame_no, value + yield self.wave_pos, value log.info("Skip %i samples that are lower than %i" % ( skip_count, self.min_volume )) + log.info("Last readed Frame is: %s" % pformat_frame_no(self.wave_pos, self.framerate)) class Bitstream2Wave(WaveBase): @@ -464,7 +467,7 @@ def get_samples(self, typecode, hz): return array.array(typecode, values) def write_wave(self, destination_filepath): - print "create wave file '%s'..." % destination_filepath + log.info("create wave file '%s'..." % destination_filepath) try: wavefile = wave.open(destination_filepath, "wb") except IOError, err: @@ -486,6 +489,7 @@ def write_wave(self, destination_filepath): raise TypeError wavefile.close() + log.info("Wave file %s written." % destination_filepath) if __name__ == "__main__": @@ -494,7 +498,6 @@ def write_wave(self, destination_filepath): verbose=False # verbose=True ) -# sys.exit() # test via CLI: From c967a0499bee2a582fbffbd43f05e1d549848a07 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Tue, 3 Sep 2013 16:17:41 +0200 Subject: [PATCH 050/151] display start/end --- PyDC/PyDC/tests.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/PyDC/PyDC/tests.py b/PyDC/PyDC/tests.py index 0f62319c..80c302b1 100755 --- a/PyDC/PyDC/tests.py +++ b/PyDC/PyDC/tests.py @@ -23,7 +23,7 @@ class TestDragon32Conversion(unittest.TestCase): def setUp(self): print - print "="*79 + print " <<<<<< unittest setUp() <<<<<< " self.base_path = os.path.normpath( os.path.join(os.path.split(configs.__file__)[0], "..") ) @@ -31,6 +31,7 @@ def setUp(self): def tearDown(self): print "\n"*2 + print " >>>>>> unittest tearDown() >>>>>> ", def _src_file_path(self, filename): return os.path.relpath( From 95ea91f53645d1c734839fc962d4e1debd643949 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Fri, 30 Aug 2013 23:47:05 +0200 Subject: [PATCH 051/151] Bugfix: use of logfilename --- PyDC/PyDC_cli.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/PyDC/PyDC_cli.py b/PyDC/PyDC_cli.py index 4e39b42a..3b091b80 100755 --- a/PyDC/PyDC_cli.py +++ b/PyDC/PyDC_cli.py @@ -71,8 +71,6 @@ def __init__(self): def parse_args(self): args = super(PyDC_CLI, self).parse_args() - self.setup_logging(args) - self.source_file = args.src print "source file.......: %s" % self.source_file @@ -91,6 +89,9 @@ def run(self): dest_ext = dest_ext.lower() self.logfilename = dest_filename + ".log" + log.info("Logfile: %s" % self.logfilename) + + self.setup_logging(self.args) # XXX: setup logging after the logfilename is set! self.cfg = Dragon32Config() From 6a084d17fad79f582ddbdf453afe8be3a7e21baa Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Fri, 30 Aug 2013 23:47:45 +0200 Subject: [PATCH 052/151] bugfix: catch IndexError, too. --- PyDC/PyDC/CassetteObjects.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/PyDC/PyDC/CassetteObjects.py b/PyDC/PyDC/CassetteObjects.py index 5e4df9ba..39d2b28d 100644 --- a/PyDC/PyDC/CassetteObjects.py +++ b/PyDC/PyDC/CassetteObjects.py @@ -151,8 +151,8 @@ def add_block_data(self, block_length, data): while True: try: line_pointer = get_word(data) - except StopIteration: - print "No line pointer information in code line data." + except (StopIteration, IndexError), err: + log.error("No line pointer information in code line data. (%s)" % err) break # print "line_pointer:", repr(line_pointer) byte_count += 2 @@ -162,8 +162,8 @@ def add_block_data(self, block_length, data): try: line_number = get_word(data) - except StopIteration: - print "No line number information in code line data." + except (StopIteration, IndexError), err: + log.error("No line number information in code line data. (%s)" % err) break # print "line_number:", repr(line_number) byte_count += 2 From 159d3171a58ba474af193ec9671471ba24f47d4f Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Sat, 31 Aug 2013 09:52:48 +0200 Subject: [PATCH 053/151] merge --analyze function --- PyDC/PyDC/__init__.py | 5 + PyDC/PyDC/base_cli.py | 2 +- PyDC/PyDC/tests.py | 28 ++++-- PyDC/PyDC/wave2bitstream.py | 179 +++++++++++++++++++++++++----------- PyDC/PyDC_cli.py | 33 +++++-- PyDC/README.creole | 56 +++++++++-- 6 files changed, 220 insertions(+), 83 deletions(-) diff --git a/PyDC/PyDC/__init__.py b/PyDC/PyDC/__init__.py index d20c1488..93aa1ac0 100644 --- a/PyDC/PyDC/__init__.py +++ b/PyDC/PyDC/__init__.py @@ -21,6 +21,11 @@ TITLE_LINE = "PyDC v%s copyleft 2013 by htfx.de - Jens Diemer, GNU GPL v3 or above" % VERSION_STRING +def analyze(wave_file, cfg): + wb = Wave2Bitstream(wave_file, cfg) + wb.analyze() + + def bas2wav(source_filepath, destination_filepath, cfg): """ Create a bitstream from a existing .bas file diff --git a/PyDC/PyDC/base_cli.py b/PyDC/PyDC/base_cli.py index ef6a957a..a513f4c8 100644 --- a/PyDC/PyDC/base_cli.py +++ b/PyDC/PyDC/base_cli.py @@ -16,7 +16,7 @@ def get_log_levels(): - levels = [5] # FIXME + levels = [5, 7] # FIXME levels += [level for level in logging._levelNames if isinstance(level, int)] return levels diff --git a/PyDC/PyDC/tests.py b/PyDC/PyDC/tests.py index 80c302b1..9825fe90 100755 --- a/PyDC/PyDC/tests.py +++ b/PyDC/PyDC/tests.py @@ -17,6 +17,7 @@ # own modules import configs from __init__ import wav2bas +from wave2bitstream import Wave2Bitstream class TestDragon32Conversion(unittest.TestCase): @@ -31,7 +32,7 @@ def setUp(self): def tearDown(self): print "\n"*2 - print " >>>>>> unittest tearDown() >>>>>> ", + print " >>>unittest tearDown() >>>", def _src_file_path(self, filename): return os.path.relpath( @@ -122,22 +123,31 @@ def test_wav2bas04(self): '63999 PRINT "END";63999\n' )) + def test_statistics(self): + source_filepath = self._src_file_path("HelloWorld1 xroar.wav") + wb = Wave2Bitstream(source_filepath, self.cfg) + statistics = wb._get_statistics(128) + self.assertEqual( + statistics, {10: 17, 11: 44, 12: 4, 19: 5, 20: 44, 21: 15} + ) + if __name__ == '__main__': - log = logging.getLogger("PyDC") - log.setLevel( - #~ logging.ERROR - logging.INFO -# logging.WARNING -# logging.DEBUG - ) - log.addHandler(logging.StreamHandler()) +# log = logging.getLogger("PyDC") +# log.setLevel( +# #~ logging.ERROR +# logging.INFO +# # logging.WARNING +# # logging.DEBUG +# ) +# log.addHandler(logging.StreamHandler()) unittest.main( argv=( sys.argv[0], # "TestDragon32Conversion.test_wav2bas01", # "TestDragon32Conversion.test_wav2bas04", +# "TestDragon32Conversion.test_statistics", ), # verbosity=1, verbosity=2, diff --git a/PyDC/PyDC/wave2bitstream.py b/PyDC/PyDC/wave2bitstream.py index 3a7c4d49..c1a5764e 100644 --- a/PyDC/PyDC/wave2bitstream.py +++ b/PyDC/PyDC/wave2bitstream.py @@ -16,7 +16,6 @@ import time import math - try: import audioop except ImportError, err: @@ -54,6 +53,14 @@ } + +def hz2duration(hz, framerate): + return int(round(float(framerate) / hz)) + +def duration2hz(duration, framerate): + return int(round(float(framerate) / duration)) + + class WaveBase(object): def get_typecode(self, samplewidth): try: @@ -129,8 +136,10 @@ def __init__(self, wave_filename, cfg): if cfg.AVG_COUNT > 1: # merge samples to a average sample - log.debug("Merge %s audio sample to one average sample" % self.avg_count) - self.avg_wave_values_generator = self.iter_avg_wave_values(self.wave_values_generator) + log.debug("Merge %s audio sample to one average sample" % cfg.AVG_COUNT) + self.avg_wave_values_generator = self.iter_avg_wave_values( + self.wave_values_generator, cfg.AVG_COUNT + ) # trigger sinus cycle self.iter_trigger_generator = self.iter_trigger(self.avg_wave_values_generator) else: @@ -140,11 +149,61 @@ def __init__(self, wave_filename, cfg): # duration of a complete sinus cycle self.iter_duration_generator = self.iter_duration(self.iter_trigger_generator) -# self.auto_sync_duration_generator = self.auto_sync_duration(self.iter_duration_generator) - # build from sinus cycle duration the bit stream self.iter_bitstream_generator = self.iter_bitstream(self.iter_duration_generator) + def _get_statistics(self, max=None): + statistics = {} + iter_duration_generator = self.iter_duration(self.iter_trigger_generator) + for count, data in enumerate(iter_duration_generator): + frame_no, duration = data + try: + statistics[duration] += 1 + except KeyError: + statistics[duration] = 1 + if max is not None and count >= max: + break + return statistics + + def analyze(self): + """ + Example output: + + 394Hz ( 28 Samples) exist: 1 + 613Hz ( 18 Samples) exist: 1 + 788Hz ( 14 Samples) exist: 1 + 919Hz ( 12 Samples) exist: 329 ********* + 1002Hz ( 11 Samples) exist: 1704 ********************************************** + 1103Hz ( 10 Samples) exist: 1256 ********************************** + 1225Hz ( 9 Samples) exist: 1743 *********************************************** + 1378Hz ( 8 Samples) exist: 1 + 1575Hz ( 7 Samples) exist: 322 ********* + 1838Hz ( 6 Samples) exist: 1851 ************************************************** + 2205Hz ( 5 Samples) exist: 1397 ************************************** + 2756Hz ( 4 Samples) exist: 913 ************************* + """ + log.debug("enable half sinus scan") + self.half_sinus = True + statistics = self._get_statistics() + + width = 50 + max_count = max(statistics.values()) + + print + print "Found this zeror crossing timings in the wave file:" + print + + for duration, count in sorted(statistics.items(), reverse=True): + hz = duration2hz(duration, self.framerate / 2) + w = int(round(float(width) / max_count * count)) + stars = "*"*w + print "%5sHz (%5s Samples) exist: %4s %s" % (hz, duration, count, stars) + + print + print "Notes:" + print " - Hz values are converted to full sinus cycle duration." + print " - Sample cound is from half sinus cycle." + def sync(self, length): """ synchronized weave sync trigger @@ -172,7 +231,6 @@ def sync(self, length): # Create only a duration list: test_durations = [i[1] for i in test_durations] -# test_durations = itertools.imap(lambda x: x[1], test_durations) diff1, diff2 = diff_info(test_durations) log.debug("sync diff info: %i vs. %i" % (diff1, diff2)) @@ -253,21 +311,24 @@ def _print_status(frame_no, bit_count): nul_hz_max = hz nul_hz_avg = average(nul_hz_avg, hz, nul_hz_count) else: - log.log(5, - "Skip signal at %s with %sSamples = %sHz" % (frame_no, duration, hz) + hz = duration2hz(duration, self.framerate) + log.log(10, # 5, + "Skip signal at %s with %sHz (%sSamples) out of frequency range." % ( + self.wave_pos, hz, duration + ) ) continue if time.time() > next_status: next_status = time.time() + 1 - _print_status(frame_no, bit_count) + _print_status(self.wave_pos, bit_count) - if frame_no == False: + if bit_count == 0: print "ERROR: No information from wave to generate the bits" print "trigger volume to high?" sys.exit(-1) - _print_status(frame_no, bit_count) + _print_status(self.wave_pos, bit_count) print log.info("\n%i Bits: %i positive bits and %i negative bits" % ( @@ -285,12 +346,12 @@ def iter_duration(self, iter_trigger): """ yield the duration of two frames in a row. """ - old_frame_no = next(iter_trigger) - for frame_no in iter_trigger: - duration = frame_no - old_frame_no - yield (frame_no, duration) - old_frame_no = frame_no - + old_pos = next(iter_trigger) + for pos in iter_trigger: + duration = pos - old_pos +# log.log(5, "Duration: %s" % duration) + yield self.wave_pos, duration + old_pos = pos def iter_trigger(self, iter_wave_values): """ @@ -327,41 +388,41 @@ def iter_trigger(self, iter_wave_values): count_sign(previous_values, 0), count_sign(next_values, 0) ] +# log.log(5, "sign info: %s" % repr(sign_info)) # yield the mid crossing if in_pos == False and sign_info == pos_null_transit: - log.log(5,"sinus curve goes from negative into positive") + log.log(5, "sinus curve goes from negative into positive") # log.debug(" %s | %s | %s" % (previous_values, mid_values, next_values)) yield mid_values[mid_index][0] in_pos = True elif in_pos == True and sign_info == neg_null_transit: if self.half_sinus: - log.log(5,"sinus curve goes from positive into negative") + log.log(5, "sinus curve goes from positive into negative") # log.debug(" %s | %s | %s" % (previous_values, mid_values, next_values)) yield mid_values[mid_index][0] in_pos = False - def iter_avg_wave_values(self, wave_values_generator): - if self.avg_count == 3: - mid_index = 1 - elif self.avg_count > 3: - mid_index = int(round(self.avg_count / 2.0)) - else: - mid_index = 0 - assert mid_index < self.avg_count - for value_tuples in iter_steps(wave_values_generator, self.avg_count): - # (frame_no, value) tuple -> value list - values = sum([i[1] for i in value_tuples]) / self.avg_count - try: - frame_no = value_tuples[mid_index][0] - except IndexError, err: - print "\nError getting %i from; %s: %s" % (mid_index, repr(value_tuples), err) - frame_no = value_tuples[0][0] - result = (frame_no, values) - if log.level >= 5: - log.log(5, "average %s samples to: %s" % (repr(value_tuples), repr(result))) -# print "average %s samples to: %s" % (repr(value_tuples), repr(result)) - yield result + def iter_avg_wave_values(self, wave_values_generator, avg_count): + if log.level >= 5: + tlm = TextLevelMeter(self.max_value, 79) + + for value_tuples in iter_steps(wave_values_generator, avg_count): + values = [i[1] for i in value_tuples] + avg_value = int(round( + float(sum(values)) / avg_count + )) + if tlm: + msg = tlm.feed(avg_value) + percent = 100.0 / self.max_value * abs(avg_value) + log.log(5, + "%s average %s samples to: %s (%.1f%%)" % ( + msg, + ",".join([str(v) for v in values]), + avg_value, percent + ) + ) + yield (self.wave_pos, avg_value) def iter_wave_values(self): """ @@ -369,7 +430,11 @@ def iter_wave_values(self): """ typecode = self.get_typecode(self.samplewidth) - tlm = TextLevelMeter(self.max_value, 79) + tlm = None + if log.level >= 5: + if self.cfg.AVG_COUNT < 2: + tlm = TextLevelMeter(self.max_value, 79) + # else: merge samples -> log output in iter_avg_wave_values # Use only a read size which is a quare divider of the samplewidth # Otherwise array.array will raise: ValueError: string length not a multiple of item size @@ -378,7 +443,6 @@ def iter_wave_values(self): if read_size != WAVE_READ_SIZE: log.info("Real use wave read size: %i Bytes" % read_size) - self.frame_no = 0 get_wave_block_func = functools.partial(self.wavefile.readframes, read_size) skip_count = 0 @@ -418,19 +482,20 @@ def iter_wave_values(self): # See: http://hg.python.org/cpython/file/482590320549/Modules/audioop.c#l957 value = value % 0xff - 128 - if abs(value) < self.min_volume: - # Ignore to lower amplitude - skip_count += 1 - continue +# if abs(value) < self.min_volume: +# # log.log(5, "Ignore to lower amplitude") +# skip_count += 1 +# continue + - if log.level >= 5: + if tlm: msg = tlm.feed(value) percent = 100.0 / self.max_value * abs(value) log.log(5, "%s value: %i (%.1f%%)" % (msg, value, percent) ) - yield self.wave_pos, value + yield (self.wave_pos, value) log.info("Skip %i samples that are lower than %i" % ( skip_count, self.min_volume @@ -498,22 +563,26 @@ def write_wave(self, destination_filepath): verbose=False # verbose=True ) + sys.exit() # test via CLI: - import sys, subprocess + import subprocess # bas -> wav - subprocess.Popen([sys.executable, "../PyDC_cli.py", "--verbosity=10", -# "--log_format=%(module)s %(lineno)d: %(message)s", - "../test_files/HelloWorld1.bas", "../test.wav" - ]).wait() +# subprocess.Popen([sys.executable, "../PyDC_cli.py", "--verbosity=10", +# # "--log_format=%(module)s %(lineno)d: %(message)s", +# "../test_files/HelloWorld1.bas", "../test.wav" +# ]).wait() # wav -> bas - subprocess.Popen([sys.executable, "../PyDC_cli.py", "--verbosity=10", + subprocess.Popen([sys.executable, "../PyDC_cli.py", + "--verbosity=10", +# "--verbosity=5", +# "--logfile=5", # "--log_format=%(module)s %(lineno)d: %(message)s", # "../test.wav", "../test.bas", - "../test_files/HelloWorld1 origin.wav", "../test_files/HelloWorld1.bas", + "../test_files/HelloWorld1 origin.wav", "--dst=../test_files/HelloWorld1.bas", ]).wait() print "-- END --" diff --git a/PyDC/PyDC_cli.py b/PyDC/PyDC_cli.py index 3b091b80..87fa6334 100755 --- a/PyDC/PyDC_cli.py +++ b/PyDC/PyDC_cli.py @@ -13,7 +13,7 @@ import os import sys -from PyDC import TITLE_LINE, VERSION_STRING, wav2bas, bas2wav +from PyDC import TITLE_LINE, VERSION_STRING, wav2bas, bas2wav, analyze from PyDC.base_cli import Base_CLI from PyDC.configs import Dragon32Config @@ -32,7 +32,16 @@ def __init__(self): super(PyDC_CLI, self).__init__() self.parser.add_argument("src", help="Source filename (.wav/.bas)") - self.parser.add_argument("dst", help="Destination filename (.wav/.bas)") + self.parser.add_argument("--dst", + help="Destination filename (.wav/.bas)" + ) + + self.parser.add_argument( + "--analyze", action="store_true", + help=( + "Display zeror crossing information in the given wave file." + ) + ) # For Wave2Bitstream(): self.parser.add_argument( @@ -74,8 +83,9 @@ def parse_args(self): self.source_file = args.src print "source file.......: %s" % self.source_file - self.destination_file = args.dst - print "destination file..: %s" % self.destination_file + if args.dst: + self.destination_file = args.dst + print "destination file..: %s" % self.destination_file return args @@ -83,12 +93,15 @@ def run(self): self.args = self.parse_args() source_filename, source_ext = os.path.splitext(self.source_file) - dest_filename, dest_ext = os.path.splitext(self.destination_file) - source_ext = source_ext.lower() - dest_ext = dest_ext.lower() - self.logfilename = dest_filename + ".log" + if self.args.dst: + dest_filename, dest_ext = os.path.splitext(self.destination_file) + dest_ext = dest_ext.lower() + + self.logfilename = dest_filename + ".log" + else: + self.logfilename = source_filename + ".log" log.info("Logfile: %s" % self.logfilename) self.setup_logging(self.args) # XXX: setup logging after the logfilename is set! @@ -101,7 +114,9 @@ def run(self): self.cfg.END_COUNT = self.args.end_count # Sample count that must be pos/neg at once self.cfg.MID_COUNT = self.args.mid_count # Sample count that can be around null - if source_ext.startswith(".wav") and dest_ext.startswith(".bas"): + if self.args.analyze: + analyze(self.source_file, self.cfg) + elif source_ext.startswith(".wav") and dest_ext.startswith(".bas"): wav2bas(self.source_file, self.destination_file, self.cfg) elif source_ext.startswith(".bas") and dest_ext.startswith(".wav"): bas2wav(self.source_file, self.destination_file, self.cfg) diff --git a/PyDC/README.creole b/PyDC/README.creole index ee1f5f06..0a5a888a 100644 --- a/PyDC/README.creole +++ b/PyDC/README.creole @@ -10,29 +10,38 @@ license: GNU GPL v3 or above, see LICENSE for more details. === PyDC_cli.py - usage {{{ -usage: PyDC_cli.py [-h] [-v] [--verbosity {0,10,20,30,40,50}] - [--logfile {0,10,20,30,40,50}] +Python dragon 32 converter 0.1.0.dev +------------------------------------------------------------------------------- + +usage: PyDC_cli.py [-h] [-v] [--verbosity {5,7,0,10,20,30,40,50}] + [--logfile {5,7,0,10,20,30,40,50}] + [--log_format LOG_FORMAT] [--dst DST] [--analyze] [--hz_variation HZ_VARIATION] [--min_volume_ratio MIN_VOLUME_RATIO] [--avg_count AVG_COUNT] [--end_count END_COUNT] [--mid_count MID_COUNT] - src dst + src Python dragon 32 converter positional arguments: src Source filename (.wav/.bas) - dst Destination filename (.wav/.bas) optional arguments: -h, --help show this help message and exit -v, --version show program's version number and exit - --verbosity {0,10,20,30,40,50} + --verbosity {5,7,0,10,20,30,40,50} verbosity level to stdout (lower == more output!) - (default: 30) - --logfile {0,10,20,30,40,50} - verbosity level to log file (lower == more output!) (default: 20) + --logfile {5,7,0,10,20,30,40,50} + verbosity level to log file (lower == more output!) + (default: 10) + --log_format LOG_FORMAT + see: http://docs.python.org/2/library/logging.html + #logrecord-attributes + --dst DST Destination filename (.wav/.bas) + --analyze Display zeror crossing information in the given wave + file. --hz_variation HZ_VARIATION How much Hz can signal scatter to match 1 or 0 bit ? (default: 450) @@ -45,11 +54,40 @@ optional arguments: Sample count that must be pos/neg at once (default: 2) --mid_count MID_COUNT Sample count that can be around null (default: 1) + +PyDC v0.1.0.dev copyleft 2013 by htfx.de - Jens Diemer, GNU GPL v3 or above }}} Example: {{{ -~$ python PyDC_cli.py FooBar.wav FooBar.bas +~$ python PyDC_cli.py FooBar.wav --dst=FooBar.bas +}}} + +To display statistics about zeror crossing counts, to this: +{{{ +~$ python PyDC_cli.py --analyze FooBar.wav +}}} + +The Output will look like this: +{{{ +Found this zeror crossing timings in the wave file: + + 394Hz ( 28 Samples) exist: 1 + 613Hz ( 18 Samples) exist: 1 + 788Hz ( 14 Samples) exist: 1 + 919Hz ( 12 Samples) exist: 329 ********* + 1002Hz ( 11 Samples) exist: 1704 ********************************************** + 1103Hz ( 10 Samples) exist: 1256 ********************************** + 1225Hz ( 9 Samples) exist: 1743 *********************************************** + 1378Hz ( 8 Samples) exist: 1 + 1575Hz ( 7 Samples) exist: 322 ********* + 1838Hz ( 6 Samples) exist: 1851 ************************************************** + 2205Hz ( 5 Samples) exist: 1397 ************************************** + 2756Hz ( 4 Samples) exist: 913 ************************* + +Notes: + - Hz values are converted to full sinus cycle duration. + - Sample cound is from half sinus cycle. }}} From 576277ccb7579a9015f090ab7c4f024c1c009fd9 Mon Sep 17 00:00:00 2001 From: Jens Diemer Date: Sat, 31 Aug 2013 13:00:15 +0200 Subject: [PATCH 054/151] Display process info while analyze, too. Conflicts: PyDC/PyDC/wave2bitstream.py --- PyDC/PyDC/utils.py | 12 ++---- PyDC/PyDC/wave2bitstream.py | 81 ++++++++++++++++++++----------------- 2 files changed, 48 insertions(+), 45 deletions(-) diff --git a/PyDC/PyDC/utils.py b/PyDC/PyDC/utils.py index 4fef02e3..6ed1b6e5 100755 --- a/PyDC/PyDC/utils.py +++ b/PyDC/PyDC/utils.py @@ -16,7 +16,7 @@ import math -LOG_FORMATTER = logging.Formatter("") # %(asctime)s %(message)s") +LOG_FORMATTER = logging.Formatter("") # %(asctime)s %(message)s") LOG_LEVEL_DICT = { 0: logging.ERROR, 1: logging.WARNING, @@ -76,12 +76,6 @@ def human_duration(t): return u"%(number).1f %(type)s" % {'number': count, 'type': name} -def pformat_frame_no(frame_no, framerate): - sec = float(frame_no) / framerate - return "%s (frame no.: %s)" % (human_duration(sec), frame_no) - - - class ProcessInfo(object): """ >>> p = ProcessInfo(100) @@ -539,7 +533,7 @@ def print_codepoint_stream(codepoint_stream, display_block_count=8, no_repr=Fals line = [] for no, codepoint in enumerate(codepoint_stream, 1): r = repr(chr(codepoint)) - if "\\x" in r: # FIXME + if "\\x" in r: # FIXME txt = "%s %i" % (hex(codepoint), codepoint) else: txt = "%s %s" % (hex(codepoint), r) @@ -604,7 +598,7 @@ def print_line(no, line, line_info): line = [] for codepoint in line_info: r = repr(chr(codepoint)) - if "\\x" in r: # FIXME + if "\\x" in r: # FIXME txt = "%s" % hex(codepoint) else: txt = "%s %s" % (hex(codepoint), r) diff --git a/PyDC/PyDC/wave2bitstream.py b/PyDC/PyDC/wave2bitstream.py index c1a5764e..81f3a426 100644 --- a/PyDC/PyDC/wave2bitstream.py +++ b/PyDC/PyDC/wave2bitstream.py @@ -26,8 +26,7 @@ # own modules from utils import average, diff_info, TextLevelMeter, iter_window, \ - human_duration, ProcessInfo, pformat_frame_no, \ - count_sign, iter_steps, sinus_values_by_hz + human_duration, ProcessInfo, count_sign, iter_steps, sinus_values_by_hz log = logging.getLogger("PyDC") @@ -152,6 +151,18 @@ def __init__(self, wave_filename, cfg): # build from sinus cycle duration the bit stream self.iter_bitstream_generator = self.iter_bitstream(self.iter_duration_generator) + def _pformat_pos(self): + sec = float(self.wave_pos) / self.framerate + return "%s (frame no.: %s)" % (human_duration(sec), self.wave_pos) + + def _print_status(self, process_info): + percent = float(self.wave_pos) / self.frame_count * 100 + rest, eta, rate = process_info.update(self.wave_pos) + sys.stdout.write("\r%.1f%% wav pos:%s - eta: %s (rate: %iFrames/sec) " % ( + percent, self._pformat_pos(), eta, rate + )) + sys.stdout.flush() + def _get_statistics(self, max=None): statistics = {} iter_duration_generator = self.iter_duration(self.iter_trigger_generator) @@ -216,7 +227,7 @@ def sync(self, length): print "Error: no bits identified!" sys.exit(-1) - log.info("First bit is at: %s" % pformat_frame_no(self.wave_pos, self.framerate)) + log.info("First bit is at: %s" % self._pformat_pos()) log.debug("enable half sinus scan") self.half_sinus = True @@ -258,17 +269,6 @@ def iter_bitstream(self, iter_duration_generator): """ assert self.half_sinus == False # Allways trigger full sinus cycle - process_info = ProcessInfo(self.frame_count, use_last_rates=4) - start_time = time.time() - next_status = start_time + 0.25 - - def _print_status(frame_no, bit_count): - ms = float(frame_no) / self.framerate - rest, eta, rate = process_info.update(frame_no) - print "%i frames (wav pos:%s) get %iBits - eta: %s (rate: %iFrames/sec)" % ( - frame_no, human_duration(ms), bit_count, eta, rate - ) - one_hz_count = 0 one_hz_min = sys.maxint one_hz_avg = None @@ -312,25 +312,18 @@ def _print_status(frame_no, bit_count): nul_hz_avg = average(nul_hz_avg, hz, nul_hz_count) else: hz = duration2hz(duration, self.framerate) - log.log(10, # 5, + log.log(7, "Skip signal at %s with %sHz (%sSamples) out of frequency range." % ( self.wave_pos, hz, duration ) ) continue - if time.time() > next_status: - next_status = time.time() + 1 - _print_status(self.wave_pos, bit_count) - if bit_count == 0: print "ERROR: No information from wave to generate the bits" print "trigger volume to high?" sys.exit(-1) - _print_status(self.wave_pos, bit_count) - print - log.info("\n%i Bits: %i positive bits and %i negative bits" % ( bit_count, one_hz_count, nul_hz_count )) @@ -346,6 +339,11 @@ def iter_duration(self, iter_trigger): """ yield the duration of two frames in a row. """ + print + process_info = ProcessInfo(self.frame_count, use_last_rates=4) + start_time = time.time() + next_status = start_time + 0.25 + old_pos = next(iter_trigger) for pos in iter_trigger: duration = pos - old_pos @@ -353,6 +351,13 @@ def iter_duration(self, iter_trigger): yield self.wave_pos, duration old_pos = pos + if time.time() > next_status: + next_status = time.time() + 1 + self._print_status(process_info) + + self._print_status(process_info) + print + def iter_trigger(self, iter_wave_values): """ trigger middle crossing of the wave sinus curve @@ -430,11 +435,12 @@ def iter_wave_values(self): """ typecode = self.get_typecode(self.samplewidth) - tlm = None if log.level >= 5: - if self.cfg.AVG_COUNT < 2: + if self.cfg.AVG_COUNT > 1: + # merge samples -> log output in iter_avg_wave_values + tlm = None + else: tlm = TextLevelMeter(self.max_value, 79) - # else: merge samples -> log output in iter_avg_wave_values # Use only a read size which is a quare divider of the samplewidth # Otherwise array.array will raise: ValueError: string length not a multiple of item size @@ -487,20 +493,12 @@ def iter_wave_values(self): # skip_count += 1 # continue - - if tlm: - msg = tlm.feed(value) - percent = 100.0 / self.max_value * abs(value) - log.log(5, - "%s value: %i (%.1f%%)" % (msg, value, percent) - ) - yield (self.wave_pos, value) log.info("Skip %i samples that are lower than %i" % ( skip_count, self.min_volume )) - log.info("Last readed Frame is: %s" % pformat_frame_no(self.wave_pos, self.framerate)) + log.info("Last readed Frame is: %s" % self._pformat_pos()) class Bitstream2Wave(WaveBase): @@ -563,11 +561,22 @@ def write_wave(self, destination_filepath): verbose=False # verbose=True ) - sys.exit() + # sys.exit() # test via CLI: - import subprocess + import sys, subprocess + +# subprocess.Popen([sys.executable, "../PyDC_cli.py", "--help"]) +# sys.exit() + + subprocess.Popen([sys.executable, "../PyDC_cli.py", "--verbosity=10", +# "--log_format=%(module)s %(lineno)d: %(message)s", + "--analyze", + "../test_files/HelloWorld1 xroar.wav" +# "../test_files/HelloWorld1 origin.wav" + ]) + sys.exit() # bas -> wav # subprocess.Popen([sys.executable, "../PyDC_cli.py", "--verbosity=10", From 45028f19f95f091a222a77e9a45a7864380c6616 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Wed, 4 Sep 2013 14:17:31 +0200 Subject: [PATCH 055/151] yield only duration in trigger --- PyDC/PyDC/wave2bitstream.py | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/PyDC/PyDC/wave2bitstream.py b/PyDC/PyDC/wave2bitstream.py index 81f3a426..1d76d33e 100644 --- a/PyDC/PyDC/wave2bitstream.py +++ b/PyDC/PyDC/wave2bitstream.py @@ -166,8 +166,7 @@ def _print_status(self, process_info): def _get_statistics(self, max=None): statistics = {} iter_duration_generator = self.iter_duration(self.iter_trigger_generator) - for count, data in enumerate(iter_duration_generator): - frame_no, duration = data + for count, duration in enumerate(iter_duration_generator): try: statistics[duration] += 1 except KeyError: @@ -240,9 +239,6 @@ def sync(self, length): test_durations = list(test_durations) - # Create only a duration list: - test_durations = [i[1] for i in test_durations] - diff1, diff2 = diff_info(test_durations) log.debug("sync diff info: %i vs. %i" % (diff1, diff2)) @@ -280,15 +276,14 @@ def iter_bitstream(self, iter_duration_generator): bit_count = 0 - frame_no = False - for frame_no, duration in iter_duration_generator: -# if frame_no > 500: -# sys.exit() + for duration in iter_duration_generator: hz = self.framerate / duration if hz > self.bit_one_min_hz and hz < self.bit_one_max_hz: log.log(5, - "bit 1 at %s in %sSamples = %sHz" % (frame_no, duration, hz) + "bit 1 at %s in %sSamples = %sHz" % ( + self._pformat_pos(), duration, hz + ) ) bit_count += 1 yield 1 @@ -300,7 +295,9 @@ def iter_bitstream(self, iter_duration_generator): one_hz_avg = average(one_hz_avg, hz, one_hz_count) elif hz > self.bit_nul_min_hz and hz < self.bit_nul_max_hz: log.log(5, - "bit 0 at %s in %sSamples = %sHz" % (frame_no, duration, hz) + "bit 0 at %s in %sSamples = %sHz" % ( + self._pformat_pos(), duration, hz + ) ) bit_count += 1 yield 0 @@ -314,7 +311,7 @@ def iter_bitstream(self, iter_duration_generator): hz = duration2hz(duration, self.framerate) log.log(7, "Skip signal at %s with %sHz (%sSamples) out of frequency range." % ( - self.wave_pos, hz, duration + self._pformat_pos(), hz, duration ) ) continue @@ -348,7 +345,7 @@ def iter_duration(self, iter_trigger): for pos in iter_trigger: duration = pos - old_pos # log.log(5, "Duration: %s" % duration) - yield self.wave_pos, duration + yield duration old_pos = pos if time.time() > next_status: From 0ca378c3f027e689cc84da02334027774db0df6a Mon Sep 17 00:00:00 2001 From: Jens Diemer Date: Sat, 31 Aug 2013 13:21:25 +0200 Subject: [PATCH 056/151] Update README --- PyDC/README.creole | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/PyDC/README.creole b/PyDC/README.creole index 0a5a888a..94af770f 100644 --- a/PyDC/README.creole +++ b/PyDC/README.creole @@ -63,6 +63,9 @@ Example: ~$ python PyDC_cli.py FooBar.wav --dst=FooBar.bas }}} + +==== analyze + To display statistics about zeror crossing counts, to this: {{{ ~$ python PyDC_cli.py --analyze FooBar.wav @@ -90,6 +93,18 @@ Notes: - Sample cound is from half sinus cycle. }}} +Some statistics from WAVES files are here: +* http://archive.worldofdragon.org/phpBB3/viewtopic.php?f=8&t=4326 + + +=== TODO + +support CAS files, too: +* CAS2BAS +* CAS2WAV +* BAS2CAS +* WAV2CAS + === Links From 3af55a47cb16bd86e7588759427cb159b052e87d Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Wed, 4 Sep 2013 14:25:20 +0200 Subject: [PATCH 057/151] move min/max Hz to method --- PyDC/PyDC/wave2bitstream.py | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/PyDC/PyDC/wave2bitstream.py b/PyDC/PyDC/wave2bitstream.py index 1d76d33e..865bc60f 100644 --- a/PyDC/PyDC/wave2bitstream.py +++ b/PyDC/PyDC/wave2bitstream.py @@ -111,20 +111,6 @@ def __init__(self, wave_filename, cfg): self.min_volume = int(round(self.max_value * cfg.MIN_VOLUME_RATIO / 100)) print "Ignore sample lower than %.1f%% = %i" % (cfg.MIN_VOLUME_RATIO, self.min_volume) - # build min/max Hz values - self.bit_nul_min_hz = cfg.BIT_NUL_HZ - cfg.HZ_VARIATION - self.bit_nul_max_hz = cfg.BIT_NUL_HZ + cfg.HZ_VARIATION - - self.bit_one_min_hz = cfg.BIT_ONE_HZ - cfg.HZ_VARIATION - self.bit_one_max_hz = cfg.BIT_ONE_HZ + cfg.HZ_VARIATION - print "bit-0 in %sHz - %sHz | bit-1 in %sHz - %sHz" % ( - self.bit_nul_min_hz, self.bit_nul_max_hz, - self.bit_one_min_hz, self.bit_one_max_hz, - ) - assert self.bit_nul_max_hz < self.bit_one_min_hz, "hz variation %sHz to big!" % ( - ((self.bit_nul_max_hz - self.bit_one_min_hz) / 2) + 1 - ) - self.half_sinus = False # in trigger yield the full cycle self.frame_no = None @@ -265,6 +251,21 @@ def iter_bitstream(self, iter_duration_generator): """ assert self.half_sinus == False # Allways trigger full sinus cycle + # build min/max Hz values + bit_nul_min_hz = self.cfg.BIT_NUL_HZ - self.cfg.HZ_VARIATION + bit_nul_max_hz = self.cfg.BIT_NUL_HZ + self.cfg.HZ_VARIATION + + bit_one_min_hz = self.cfg.BIT_ONE_HZ - self.cfg.HZ_VARIATION + bit_one_max_hz = self.cfg.BIT_ONE_HZ + self.cfg.HZ_VARIATION + print "bit-0 in %sHz - %sHz | bit-1 in %sHz - %sHz" % ( + bit_nul_min_hz, bit_nul_max_hz, + bit_one_min_hz, bit_one_max_hz, + ) + assert bit_nul_max_hz < bit_one_min_hz, "hz variation %sHz to big!" % ( + ((bit_nul_max_hz - bit_one_min_hz) / 2) + 1 + ) + + # for end statistics one_hz_count = 0 one_hz_min = sys.maxint one_hz_avg = None @@ -279,7 +280,7 @@ def iter_bitstream(self, iter_duration_generator): for duration in iter_duration_generator: hz = self.framerate / duration - if hz > self.bit_one_min_hz and hz < self.bit_one_max_hz: + if hz > bit_one_min_hz and hz < bit_one_max_hz: log.log(5, "bit 1 at %s in %sSamples = %sHz" % ( self._pformat_pos(), duration, hz @@ -293,7 +294,7 @@ def iter_bitstream(self, iter_duration_generator): if hz > one_hz_max: one_hz_max = hz one_hz_avg = average(one_hz_avg, hz, one_hz_count) - elif hz > self.bit_nul_min_hz and hz < self.bit_nul_max_hz: + elif hz > bit_nul_min_hz and hz < bit_nul_max_hz: log.log(5, "bit 0 at %s in %sSamples = %sHz" % ( self._pformat_pos(), duration, hz From 89eb4985804fa0761db8c0e3cf334c6683ec096c Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Wed, 4 Sep 2013 15:18:29 +0200 Subject: [PATCH 058/151] use duration instead of Hz to trigger bits --- PyDC/PyDC/utils.py | 20 +++++++++++++ PyDC/PyDC/wave2bitstream.py | 57 +++++++++++++++++++++---------------- 2 files changed, 52 insertions(+), 25 deletions(-) diff --git a/PyDC/PyDC/utils.py b/PyDC/PyDC/utils.py index 6ed1b6e5..57354546 100755 --- a/PyDC/PyDC/utils.py +++ b/PyDC/PyDC/utils.py @@ -754,6 +754,26 @@ def sinus_values_by_hz(framerate, hz, max_value): return values +def hz2duration(hz, framerate): + """ + >>> hz2duration(hz=1200, framerate=44100) + 37 + >>> hz2duration(hz=2400, framerate=44100) + 18 + """ + return int(round(float(framerate) / hz)) + + +def duration2hz(duration, framerate): + """ + >>> duration2hz(duration=37, framerate=44100) + 1192 + >>> duration2hz(duration=18, framerate=44100) + 2450 + """ + return int(round(float(framerate) / duration)) + + if __name__ == "__main__": import doctest print doctest.testmod() diff --git a/PyDC/PyDC/wave2bitstream.py b/PyDC/PyDC/wave2bitstream.py index 865bc60f..c5b6bddf 100644 --- a/PyDC/PyDC/wave2bitstream.py +++ b/PyDC/PyDC/wave2bitstream.py @@ -26,7 +26,8 @@ # own modules from utils import average, diff_info, TextLevelMeter, iter_window, \ - human_duration, ProcessInfo, count_sign, iter_steps, sinus_values_by_hz + human_duration, ProcessInfo, count_sign, iter_steps, sinus_values_by_hz, \ + hz2duration, duration2hz log = logging.getLogger("PyDC") @@ -52,14 +53,6 @@ } - -def hz2duration(hz, framerate): - return int(round(float(framerate) / hz)) - -def duration2hz(duration, framerate): - return int(round(float(framerate) / duration)) - - class WaveBase(object): def get_typecode(self, samplewidth): try: @@ -141,6 +134,12 @@ def _pformat_pos(self): sec = float(self.wave_pos) / self.framerate return "%s (frame no.: %s)" % (human_duration(sec), self.wave_pos) + def _hz2duration(self, hz): + return hz2duration(hz, framerate=self.framerate) + + def _duration2hz(self, duration): + return duration2hz(duration, framerate=self.framerate) + def _print_status(self, process_info): percent = float(self.wave_pos) / self.frame_count * 100 rest, eta, rate = process_info.update(self.wave_pos) @@ -257,13 +256,21 @@ def iter_bitstream(self, iter_duration_generator): bit_one_min_hz = self.cfg.BIT_ONE_HZ - self.cfg.HZ_VARIATION bit_one_max_hz = self.cfg.BIT_ONE_HZ + self.cfg.HZ_VARIATION - print "bit-0 in %sHz - %sHz | bit-1 in %sHz - %sHz" % ( - bit_nul_min_hz, bit_nul_max_hz, - bit_one_min_hz, bit_one_max_hz, - ) - assert bit_nul_max_hz < bit_one_min_hz, "hz variation %sHz to big!" % ( + + bit_nul_max_duration = self._hz2duration(bit_nul_min_hz) + bit_nul_min_duration = self._hz2duration(bit_nul_max_hz) + + bit_one_max_duration = self._hz2duration(bit_one_min_hz) + bit_one_min_duration = self._hz2duration(bit_one_max_hz) + + log.info("bit-0 in %sHz - %sHz (duration: %s-%s) | bit-1 in %sHz - %sHz (duration: %s-%s)" % ( + bit_nul_min_hz, bit_nul_max_hz, bit_nul_min_duration, bit_nul_max_duration, + bit_one_min_hz, bit_one_max_hz, bit_one_min_duration, bit_one_max_duration, + )) + assert bit_nul_max_hz < bit_one_min_hz, "HZ_VARIATION value is %sHz too high!" % ( ((bit_nul_max_hz - bit_one_min_hz) / 2) + 1 ) + assert bit_one_max_duration < bit_nul_min_duration, "HZ_VARIATION value is too high!" # for end statistics one_hz_count = 0 @@ -279,8 +286,8 @@ def iter_bitstream(self, iter_duration_generator): for duration in iter_duration_generator: - hz = self.framerate / duration - if hz > bit_one_min_hz and hz < bit_one_max_hz: + if bit_one_min_duration < duration < bit_one_max_duration: + hz = self._duration2hz(duration) log.log(5, "bit 1 at %s in %sSamples = %sHz" % ( self._pformat_pos(), duration, hz @@ -294,7 +301,8 @@ def iter_bitstream(self, iter_duration_generator): if hz > one_hz_max: one_hz_max = hz one_hz_avg = average(one_hz_avg, hz, one_hz_count) - elif hz > bit_nul_min_hz and hz < bit_nul_max_hz: + elif bit_nul_min_duration < duration < bit_nul_max_duration: + hz = self._duration2hz(duration) log.log(5, "bit 0 at %s in %sSamples = %sHz" % ( self._pformat_pos(), duration, hz @@ -309,7 +317,7 @@ def iter_bitstream(self, iter_duration_generator): nul_hz_max = hz nul_hz_avg = average(nul_hz_avg, hz, nul_hz_count) else: - hz = duration2hz(duration, self.framerate) + hz = self._duration2hz(duration) log.log(7, "Skip signal at %s with %sHz (%sSamples) out of frequency range." % ( self._pformat_pos(), hz, duration @@ -568,13 +576,12 @@ def write_wave(self, destination_filepath): # subprocess.Popen([sys.executable, "../PyDC_cli.py", "--help"]) # sys.exit() - subprocess.Popen([sys.executable, "../PyDC_cli.py", "--verbosity=10", -# "--log_format=%(module)s %(lineno)d: %(message)s", - "--analyze", - "../test_files/HelloWorld1 xroar.wav" -# "../test_files/HelloWorld1 origin.wav" - ]) - sys.exit() +# subprocess.Popen([sys.executable, "../PyDC_cli.py", "--verbosity=10", +# # "--log_format=%(module)s %(lineno)d: %(message)s", +# "--analyze", +# "../test_files/HelloWorld1 xroar.wav" +# # "../test_files/HelloWorld1 origin.wav" +# ]) # bas -> wav # subprocess.Popen([sys.executable, "../PyDC_cli.py", "--verbosity=10", From 0897d3c8cc656387f02faabd036b6c92c6534c7a Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Wed, 4 Sep 2013 18:15:24 +0200 Subject: [PATCH 059/151] bugfix in bas2wav, but still don't work, yet :( --- PyDC/PyDC/CassetteObjects.py | 70 ++++++++++++------ PyDC/PyDC/__init__.py | 11 ++- PyDC/PyDC/bitstream_handler.py | 15 +++- PyDC/PyDC/tests.py | 4 + PyDC/PyDC/wave2bitstream.py | 131 +++++++++++++++++++++------------ 5 files changed, 155 insertions(+), 76 deletions(-) diff --git a/PyDC/PyDC/CassetteObjects.py b/PyDC/PyDC/CassetteObjects.py index 39d2b28d..cabeb107 100644 --- a/PyDC/PyDC/CassetteObjects.py +++ b/PyDC/PyDC/CassetteObjects.py @@ -349,8 +349,8 @@ def get_filename_block_as_codepoints(self): codepoints.append(self.cfg.BASIC_ASCII) # one byte ASCII flag codepoints.append(0x00) # one byte gap flag (00=no gaps, FF=gaps) # FIXME, see: http://five.pairlist.net/pipermail/coco/2013-August/070938.html - codepoints += [0x00, 0x00] # two bytes machine code starting address - codepoints += [0x00, 0x00] # two bytes machine code loading address + codepoints += [0x0c, 0x00] # two bytes machine code starting address + codepoints += [0x0c, 0x00] # two bytes machine code loading address log.debug("filename block: %s" % pformat_codepoints(codepoints)) return codepoints @@ -409,6 +409,7 @@ def __init__(self, cfg): self.cfg = cfg self.files = [] self.current_file = None + self.wav = None # Bitstream2Wave instance only if write_wave() used! def add_from_bas(self, filename): with open(filename, "r") as f: @@ -440,10 +441,12 @@ def block2codepoint_stream(self, block_type, block_codepoints): log.debug("yield %sx lead byte %s" % ( self.cfg.LEAD_BYTE_LEN, hex(self.cfg.LEAD_BYTE_CODEPOINT) )) - for count in xrange(self.cfg.LEAD_BYTE_LEN): - yield self.cfg.LEAD_BYTE_CODEPOINT - + leadin = [self.cfg.LEAD_BYTE_CODEPOINT for _ in xrange(self.cfg.LEAD_BYTE_LEN)] + yield leadin + log.debug("yield sync byte %s" % hex(self.cfg.SYNC_BYTE_CODEPOINT)) + if self.wav: + log.debug("wave pos: %s" % self.wav.pformat_pos()) yield self.cfg.SYNC_BYTE_CODEPOINT log.debug("yield block type '%s'" % self.cfg.BLOCK_TYPE_DICT[block_type]) @@ -464,14 +467,15 @@ def block2codepoint_stream(self, block_type, block_codepoints): log.debug("yield magic byte %s" % hex(self.cfg.MAGIC_BYTE)) yield self.cfg.MAGIC_BYTE # 0x55 else: - log.debug("yield '%s':" % self.cfg.BLOCK_TYPE_DICT[block_type]) + log.debug("content of '%s':" % self.cfg.BLOCK_TYPE_DICT[block_type]) log.debug("-"*79) log.debug(pformat_codepoints(block_codepoints)) log.debug("-"*79) checksum = 0x00 - for codepoint in block_codepoints: - checksum += codepoint - yield codepoint + yield block_codepoints +# for codepoint in block_codepoints: +# checksum += codepoint +# yield codepoint checksum += block_type checksum += block_length @@ -490,6 +494,9 @@ def codepoint_stream(self): ): yield codepoints + if self.wav: + self.wav.write_silence(sec=2) + # yield file content for codepoints in self.block2codepoint_stream( block_type=self.cfg.DATA_BLOCK, @@ -497,6 +504,9 @@ def codepoint_stream(self): ): yield codepoints + if self.wav: + self.wav.write_silence(sec=2) + # yield EOF for codepoints in self.block2codepoint_stream( block_type=self.cfg.EOF_BLOCK, @@ -504,11 +514,19 @@ def codepoint_stream(self): ): yield codepoints - def get_as_bitstream(self): + if self.wav: + self.wav.write_silence(sec=2) + + def write_wave(self, wav): + self.wav = wav # Bitstream2Wave instance for codepoint in self.codepoint_stream(): - assert isinstance(codepoint, int), "Codepoint %s is not int/hex" % repr(codepoint) - for bit in codepoints2bitstream(codepoint): - yield bit + if isinstance(codepoint, (tuple, list)): + for item in codepoint: + assert isinstance(item, int), "Codepoint %s is not int/hex" % repr(codepoint) + else: + assert isinstance(codepoint, int), "Codepoint %s is not int/hex" % repr(codepoint) + wav.write_codepoint(codepoint) + def save_bas(self, destination_file): dest_filename, dest_ext = os.path.splitext(destination_file) @@ -549,17 +567,27 @@ def pprint_codepoint_stream(self): # ) # sys.exit() - import time, subprocess - subprocess.Popen([sys.executable, "../PyDC_cli.py", "--verbosity=10", - # bas -> wav - "../test_files/HelloWorld1.bas", "../test.wav" + import subprocess + + # bas -> wav + subprocess.Popen([sys.executable, "../PyDC_cli.py", + "--verbosity=10", +# "--verbosity=5", +# "--logfile=5", +# "--log_format=%(module)s %(lineno)d: %(message)s", + "../test_files/HelloWorld1.bas", "--dst=../test.wav" ]).wait() + print "\n"*3 print "="*79 - subprocess.Popen([sys.executable, "../PyDC_cli.py", "--verbosity=10", - # wav -> bas - "../test.wav", "../test.bas", -# "../test_files/HelloWorld1 origin.wav", "../test_files/HelloWorld1.bas", + print "\n"*3 + +# # wav -> bas + subprocess.Popen([sys.executable, "../PyDC_cli.py", +# "--verbosity=10", + "--verbosity=7", + "../test.wav", "--dst=../test.bas", +# "../test_files/HelloWorld1 origin.wav", "--dst=../test_files/HelloWorld1.bas", ]).wait() print "-- END --" diff --git a/PyDC/PyDC/__init__.py b/PyDC/PyDC/__init__.py index 93aa1ac0..b0b069ea 100644 --- a/PyDC/PyDC/__init__.py +++ b/PyDC/PyDC/__init__.py @@ -35,11 +35,14 @@ def bas2wav(source_filepath, destination_filepath, cfg): c.add_from_bas(source_filepath) c.print_debug_info() - bitstream = c.get_as_bitstream() -# print_bitlist(bitstream) - bw = Bitstream2Wave(bitstream, cfg) - bw.write_wave(destination_filepath) + wav = Bitstream2Wave(destination_filepath, cfg) + + c.write_wave(wav) + + wav.close() + + def wav2bas(source_filepath, destination_filepath, cfg): diff --git a/PyDC/PyDC/bitstream_handler.py b/PyDC/PyDC/bitstream_handler.py index 34de2ab1..9542a568 100755 --- a/PyDC/PyDC/bitstream_handler.py +++ b/PyDC/PyDC/bitstream_handler.py @@ -107,6 +107,7 @@ def feed(self, bitstream): block_type, block_length, codepoints = self.get_block_info(bitstream) except SyncByteNotFoundError, err: log.error(err) + log.info("Last wave pos: %s" % bitstream.pformat_pos()) break try: @@ -138,11 +139,14 @@ def feed(self, bitstream): print "="*79 def sync_bitstream(self, bitstream): + log.debug("start sync bitstream at wave pos: %s" % bitstream.pformat_pos()) bitstream.sync(32) # Sync bitstream to wave sinus cycle # test_bitstream = list(itertools.islice(bitstream, 258 * 8)) # print_bitlist(test_bitstream) + log.debug("Searching for lead-in byte at wave pos: %s" % bitstream.pformat_pos()) + # Searching for lead-in byte lead_in_pattern = list(codepoints2bitstream(self.cfg.LEAD_BYTE_CODEPOINT)) max_pos = self.cfg.LEAD_BYTE_LEN * 8 @@ -158,10 +162,13 @@ def sync_bitstream(self, bitstream): list2str(lead_in_pattern), hex(self.cfg.LEAD_BYTE_CODEPOINT), err )) else: - log.info("Leader-Byte '%s' (%s) found at %i Bytes" % ( - list2str(lead_in_pattern), hex(self.cfg.LEAD_BYTE_CODEPOINT), leader_pos + log.info("Leader-Byte '%s' (%s) found at %i Bytes (wave pos: %s)" % ( + list2str(lead_in_pattern), hex(self.cfg.LEAD_BYTE_CODEPOINT), + leader_pos, bitstream.pformat_pos() )) + log.debug("Search for sync-byte at wave pos: %s" % bitstream.pformat_pos()) + # Search for sync-byte sync_pattern = list(codepoints2bitstream(self.cfg.SYNC_BYTE_CODEPOINT)) max_search_bits = self.cfg.MAX_SYNC_BYTE_SEARCH * 8 @@ -182,9 +189,9 @@ def sync_bitstream(self, bitstream): ) ) else: - log.info("Sync-Byte '%s' (%s) found at %i Bytes" % ( + log.info("Sync-Byte '%s' (%s) found at %i Bytes (wave pos: %s)" % ( list2str(sync_pattern), hex(self.cfg.SYNC_BYTE_CODEPOINT), - sync_pos + sync_pos, bitstream.pformat_pos() )) diff --git a/PyDC/PyDC/tests.py b/PyDC/PyDC/tests.py index 9825fe90..fe801a99 100755 --- a/PyDC/PyDC/tests.py +++ b/PyDC/PyDC/tests.py @@ -131,6 +131,9 @@ def test_statistics(self): statistics, {10: 17, 11: 44, 12: 4, 19: 5, 20: 44, 21: 15} ) +# def test_bas2ascii_wav(self): +# pass # TODO + if __name__ == '__main__': # log = logging.getLogger("PyDC") @@ -148,6 +151,7 @@ def test_statistics(self): # "TestDragon32Conversion.test_wav2bas01", # "TestDragon32Conversion.test_wav2bas04", # "TestDragon32Conversion.test_statistics", +# "TestDragon32Conversion.test_bas2ascii_wav", # TODO ), # verbosity=1, verbosity=2, diff --git a/PyDC/PyDC/wave2bitstream.py b/PyDC/PyDC/wave2bitstream.py index c5b6bddf..f9289acd 100644 --- a/PyDC/PyDC/wave2bitstream.py +++ b/PyDC/PyDC/wave2bitstream.py @@ -27,7 +27,7 @@ # own modules from utils import average, diff_info, TextLevelMeter, iter_window, \ human_duration, ProcessInfo, count_sign, iter_steps, sinus_values_by_hz, \ - hz2duration, duration2hz + hz2duration, duration2hz, codepoints2bitstream, bits2codepoint log = logging.getLogger("PyDC") @@ -65,6 +65,16 @@ def get_typecode(self, samplewidth): ) return typecode + def pformat_pos(self): + sec = float(self.wave_pos) / self.framerate + return "%s (frame no.: %s)" % (human_duration(sec), self.wave_pos) + + def _hz2duration(self, hz): + return hz2duration(hz, framerate=self.framerate) + + def _duration2hz(self, duration): + return duration2hz(duration, framerate=self.framerate) + class Wave2Bitstream(WaveBase): @@ -130,21 +140,11 @@ def __init__(self, wave_filename, cfg): # build from sinus cycle duration the bit stream self.iter_bitstream_generator = self.iter_bitstream(self.iter_duration_generator) - def _pformat_pos(self): - sec = float(self.wave_pos) / self.framerate - return "%s (frame no.: %s)" % (human_duration(sec), self.wave_pos) - - def _hz2duration(self, hz): - return hz2duration(hz, framerate=self.framerate) - - def _duration2hz(self, duration): - return duration2hz(duration, framerate=self.framerate) - def _print_status(self, process_info): percent = float(self.wave_pos) / self.frame_count * 100 rest, eta, rate = process_info.update(self.wave_pos) sys.stdout.write("\r%.1f%% wav pos:%s - eta: %s (rate: %iFrames/sec) " % ( - percent, self._pformat_pos(), eta, rate + percent, self.pformat_pos(), eta, rate )) sys.stdout.flush() @@ -211,7 +211,7 @@ def sync(self, length): print "Error: no bits identified!" sys.exit(-1) - log.info("First bit is at: %s" % self._pformat_pos()) + log.info("First bit is at: %s" % self.pformat_pos()) log.debug("enable half sinus scan") self.half_sinus = True @@ -290,7 +290,7 @@ def iter_bitstream(self, iter_duration_generator): hz = self._duration2hz(duration) log.log(5, "bit 1 at %s in %sSamples = %sHz" % ( - self._pformat_pos(), duration, hz + self.pformat_pos(), duration, hz ) ) bit_count += 1 @@ -305,7 +305,7 @@ def iter_bitstream(self, iter_duration_generator): hz = self._duration2hz(duration) log.log(5, "bit 0 at %s in %sSamples = %sHz" % ( - self._pformat_pos(), duration, hz + self.pformat_pos(), duration, hz ) ) bit_count += 1 @@ -320,7 +320,7 @@ def iter_bitstream(self, iter_duration_generator): hz = self._duration2hz(duration) log.log(7, "Skip signal at %s with %sHz (%sSamples) out of frequency range." % ( - self._pformat_pos(), hz, duration + self.pformat_pos(), hz, duration ) ) continue @@ -504,12 +504,12 @@ def iter_wave_values(self): log.info("Skip %i samples that are lower than %i" % ( skip_count, self.min_volume )) - log.info("Last readed Frame is: %s" % self._pformat_pos()) + log.info("Last readed Frame is: %s" % self.pformat_pos()) class Bitstream2Wave(WaveBase): - def __init__(self, bitstream, cfg): - self.bitstream = bitstream + def __init__(self, destination_filepath, cfg): + self.destination_filepath = destination_filepath self.cfg = cfg wave_max_value = MAX_VALUES[self.cfg.SAMPLEWIDTH] @@ -522,43 +522,69 @@ def __init__(self, bitstream, cfg): self.used_max_values, self.cfg.VOLUME_RATIO )) - typecode = self.get_typecode(self.cfg.SAMPLEWIDTH) + self.typecode = self.get_typecode(self.cfg.SAMPLEWIDTH) - self.bit_nul_samples = self.get_samples(typecode, self.cfg.BIT_NUL_HZ) - self.bit_one_samples = self.get_samples(typecode, self.cfg.BIT_ONE_HZ) + self.bit_nul_samples = self.get_samples(self.cfg.BIT_NUL_HZ) + self.bit_one_samples = self.get_samples(self.cfg.BIT_ONE_HZ) - def get_samples(self, typecode, hz): - values = tuple( - sinus_values_by_hz(self.cfg.FRAMERATE, hz, self.used_max_values) - ) - real_hz = float(self.cfg.FRAMERATE) / len(values) - log.debug("Real frequency: %.2f" % real_hz) - return array.array(typecode, values) - - def write_wave(self, destination_filepath): log.info("create wave file '%s'..." % destination_filepath) try: - wavefile = wave.open(destination_filepath, "wb") + self.wavefile = wave.open(destination_filepath, "wb") except IOError, err: log.error("Error opening %s: %s" % (repr(destination_filepath), err)) sys.exit(-1) - wavefile.setnchannels(1) # Mono - wavefile.setsampwidth(self.cfg.SAMPLEWIDTH) - wavefile.setframerate(self.cfg.FRAMERATE) + self.wavefile.setnchannels(1) # Mono + self.wavefile.setsampwidth(self.cfg.SAMPLEWIDTH) + self.wavefile.setframerate(self.cfg.FRAMERATE) + self.framerate = self.cfg.FRAMERATE + + self.wave_pos = self.wavefile._nframeswritten - for bit in self.bitstream: + def get_samples(self, hz): + values = tuple( + sinus_values_by_hz(self.cfg.FRAMERATE, hz, self.used_max_values) + ) + real_hz = float(self.cfg.FRAMERATE) / len(values) + log.debug("Real frequency: %.2f" % real_hz) + return array.array(self.typecode, values) + + def write_codepoint(self, codepoints): + written_codepoints = [] + bits = [] + for bit in codepoints2bitstream(codepoints): + bits.append(bit) + if len(bits) == 8: + written_codepoints.append(bits2codepoint(bits)) + bits = [] + if bit == 0: # wavefile.writeframes(self.bit_nul_samples) - wavefile.writeframesraw(self.bit_nul_samples) + self.wavefile.writeframes(self.bit_nul_samples) elif bit == 1: # wavefile.writeframes(self.bit_one_samples) - wavefile.writeframesraw(self.bit_one_samples) + self.wavefile.writeframes(self.bit_one_samples) else: raise TypeError - - wavefile.close() - log.info("Wave file %s written." % destination_filepath) + log.debug("Written at %s: %s" % ( + self.pformat_pos(), ",".join([hex(x) for x in written_codepoints]) + )) + self.wave_pos = self.wavefile._nframeswritten + + def write_silence(self, sec): + # the filename block was written + silence = [0x00 for _ in xrange(sec * self.framerate)] + silence = array.array(self.typecode, silence) + self.wavefile.writeframes(silence) + log.debug("Write %ssec. silence at %s" % (sec, self.pformat_pos())) + self.wave_pos = self.wavefile._nframeswritten + + def close(self): + self.wavefile.close() + self.wave_pos = self.wavefile._nframeswritten + log.info("Wave file %s written (%s)" % ( + self.destination_filepath, self.pformat_pos() + )) if __name__ == "__main__": @@ -583,11 +609,22 @@ def write_wave(self, destination_filepath): # # "../test_files/HelloWorld1 origin.wav" # ]) +# print "\n"*3 +# print "="*79 +# print "\n"*3 + # bas -> wav -# subprocess.Popen([sys.executable, "../PyDC_cli.py", "--verbosity=10", -# # "--log_format=%(module)s %(lineno)d: %(message)s", -# "../test_files/HelloWorld1.bas", "../test.wav" -# ]).wait() + subprocess.Popen([sys.executable, "../PyDC_cli.py", + "--verbosity=10", +# "--verbosity=5", +# "--logfile=5", +# "--log_format=%(module)s %(lineno)d: %(message)s", + "../test_files/HelloWorld1.bas", "--dst=../test.wav" + ]).wait() + + print "\n"*3 + print "="*79 + print "\n"*3 # wav -> bas subprocess.Popen([sys.executable, "../PyDC_cli.py", @@ -595,8 +632,8 @@ def write_wave(self, destination_filepath): # "--verbosity=5", # "--logfile=5", # "--log_format=%(module)s %(lineno)d: %(message)s", -# "../test.wav", "../test.bas", - "../test_files/HelloWorld1 origin.wav", "--dst=../test_files/HelloWorld1.bas", + "../test.wav", "--dst=../test.bas", +# "../test_files/HelloWorld1 origin.wav", "--dst=../test_files/HelloWorld1.bas", ]).wait() print "-- END --" From 66739723a43580970bbb556c558215f522b9dba8 Mon Sep 17 00:00:00 2001 From: Jens Diemer Date: Wed, 4 Sep 2013 22:55:30 +0200 Subject: [PATCH 060/151] add bas2cas --- PyDC/PyDC/CassetteObjects.py | 10 ++++++++++ PyDC/PyDC/__init__.py | 12 ++++++++++-- PyDC/PyDC_cli.py | 8 ++++++-- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/PyDC/PyDC/CassetteObjects.py b/PyDC/PyDC/CassetteObjects.py index cabeb107..ef4a0b3a 100644 --- a/PyDC/PyDC/CassetteObjects.py +++ b/PyDC/PyDC/CassetteObjects.py @@ -527,6 +527,16 @@ def write_wave(self, wav): assert isinstance(codepoint, int), "Codepoint %s is not int/hex" % repr(codepoint) wav.write_codepoint(codepoint) + def write_cas(self, destination_file): + log.info("Create %s..." % repr(destination_file)) + with open(destination_file, "wb") as f: + for codepoint in self.codepoint_stream(): + if isinstance(codepoint, (tuple, list)): + for item in codepoint: + f.write(chr(item)) + else: + f.write(chr(codepoint)) + print "\nFile %s saved." % repr(destination_file) def save_bas(self, destination_file): dest_filename, dest_ext = os.path.splitext(destination_file) diff --git a/PyDC/PyDC/__init__.py b/PyDC/PyDC/__init__.py index b0b069ea..daddb045 100644 --- a/PyDC/PyDC/__init__.py +++ b/PyDC/PyDC/__init__.py @@ -25,12 +25,20 @@ def analyze(wave_file, cfg): wb = Wave2Bitstream(wave_file, cfg) wb.analyze() +def bas2cas(source_filepath, destination_filepath, cfg): + """ + Create a .cas file from a existing .bas file + """ + c = Cassette(cfg) + c.add_from_bas(source_filepath) + c.print_debug_info() + c.write_cas(destination_filepath) + def bas2wav(source_filepath, destination_filepath, cfg): """ - Create a bitstream from a existing .bas file + Create a wave file from a existing .bas file """ - c = Cassette(cfg) c.add_from_bas(source_filepath) diff --git a/PyDC/PyDC_cli.py b/PyDC/PyDC_cli.py index 87fa6334..9b374cb9 100755 --- a/PyDC/PyDC_cli.py +++ b/PyDC/PyDC_cli.py @@ -13,7 +13,7 @@ import os import sys -from PyDC import TITLE_LINE, VERSION_STRING, wav2bas, bas2wav, analyze +from PyDC import TITLE_LINE, VERSION_STRING, wav2bas, bas2wav, analyze, bas2cas from PyDC.base_cli import Base_CLI from PyDC.configs import Dragon32Config @@ -33,7 +33,7 @@ def __init__(self): self.parser.add_argument("src", help="Source filename (.wav/.bas)") self.parser.add_argument("--dst", - help="Destination filename (.wav/.bas)" + help="Destination filename (.wav/.bas/.cas)" ) self.parser.add_argument( @@ -116,10 +116,14 @@ def run(self): if self.args.analyze: analyze(self.source_file, self.cfg) + elif source_ext.startswith(".wav") and dest_ext.startswith(".bas"): wav2bas(self.source_file, self.destination_file, self.cfg) elif source_ext.startswith(".bas") and dest_ext.startswith(".wav"): bas2wav(self.source_file, self.destination_file, self.cfg) + + elif source_ext.startswith(".bas") and dest_ext.startswith(".cas"): + bas2cas(self.source_file, self.destination_file, self.cfg) else: print "ERROR:" print "%s to %s ???" % (repr(self.source_file), repr(self.destination_file)) From aca7379819e849ab4f7729e6b8eb53e40041933e Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Thu, 5 Sep 2013 18:27:09 +0200 Subject: [PATCH 061/151] start cas2bas --- PyDC/PyDC/CassetteObjects.py | 77 ++++++++++++++++++++++++++++++------ PyDC/PyDC/__init__.py | 10 +++++ PyDC/PyDC_cli.py | 6 ++- 3 files changed, 79 insertions(+), 14 deletions(-) diff --git a/PyDC/PyDC/CassetteObjects.py b/PyDC/PyDC/CassetteObjects.py index ef4a0b3a..5755947d 100644 --- a/PyDC/PyDC/CassetteObjects.py +++ b/PyDC/PyDC/CassetteObjects.py @@ -419,6 +419,55 @@ def add_from_bas(self, filename): self.current_file.create_from_bas(filename, file_content) self.files.append(self.current_file) + def add_from_cas(self, source_filepath): + log.debug("Open %s..." % repr(source_filepath)) + + class CasStream(object): + def __init__(self, source_filepath): + self.source_filepath = source_filepath + self.f = open(source_filepath, "rb") + self.pos = 0 + + def __iter__(self): + return self + def next(self): + self.last_byte = self.f.read(1) + if self.last_byte == "": + raise StopIteration + self.pos += 1 + self.last_codepoint = ord(self.last_byte) + return (self.pos, self.last_codepoint) + + cas_stream = CasStream(source_filepath) + + leadin_count = None +# for i in iter(cas_stream.next, self.cfg.LEAD_BYTE_CODEPOINT): +# print i +# break + + log.debug("Count leading byte.") +# for leadin_count, leadin_byte in iter(cas_stream.next, self.cfg.LEAD_BYTE_CODEPOINT): + for leadin_count, leadin_byte in cas_stream: + if leadin_byte != self.cfg.LEAD_BYTE_CODEPOINT: + break + + if leadin_count is None: + log.error("Leadin byte not found in file!") + sys.exit(-1) + log.info("%s x leadin bytes found." % leadin_count) + +# sync_byte = cas_stream.next() + sync_byte = cas_stream.last_codepoint + if sync_byte != self.cfg.SYNC_BYTE_CODEPOINT: + log.error("Sync byte wrong. Get %s but excepted %s" % ( + repr(sync_byte), hex(self.cfg.SYNC_BYTE_CODEPOINT) + )) + else: + log.debug("Sync byte, ok.") + + raise TODO + + def add_block(self, block_type, block_length, block_codepoints): if block_type == self.cfg.EOF_BLOCK: return @@ -579,24 +628,26 @@ def pprint_codepoint_stream(self): import subprocess - # bas -> wav - subprocess.Popen([sys.executable, "../PyDC_cli.py", - "--verbosity=10", -# "--verbosity=5", -# "--logfile=5", -# "--log_format=%(module)s %(lineno)d: %(message)s", - "../test_files/HelloWorld1.bas", "--dst=../test.wav" - ]).wait() - - print "\n"*3 - print "="*79 - print "\n"*3 +# # bas -> wav +# subprocess.Popen([sys.executable, "../PyDC_cli.py", +# "--verbosity=10", +# # "--verbosity=5", +# # "--logfile=5", +# # "--log_format=%(module)s %(lineno)d: %(message)s", +# # "../test_files/HelloWorld1.bas", "--dst=../test.wav" +# "../test_files/HelloWorld1.bas", "--dst=../test.cas" +# ]).wait() +# +# print "\n"*3 +# print "="*79 +# print "\n"*3 # # wav -> bas subprocess.Popen([sys.executable, "../PyDC_cli.py", # "--verbosity=10", "--verbosity=7", - "../test.wav", "--dst=../test.bas", +# "../test.wav", "--dst=../test.bas", + "../test.cas", "--dst=../test.bas", # "../test_files/HelloWorld1 origin.wav", "--dst=../test_files/HelloWorld1.bas", ]).wait() diff --git a/PyDC/PyDC/__init__.py b/PyDC/PyDC/__init__.py index daddb045..2cd9c155 100644 --- a/PyDC/PyDC/__init__.py +++ b/PyDC/PyDC/__init__.py @@ -35,6 +35,16 @@ def bas2cas(source_filepath, destination_filepath, cfg): c.write_cas(destination_filepath) +def cas2bas(source_filepath, destination_filepath, cfg): + """ + Read .cas file and create a .bas file + """ + c = Cassette(cfg) + c.add_from_cas(source_filepath) + c.print_debug_info() + c.save_bas(destination_filepath) + + def bas2wav(source_filepath, destination_filepath, cfg): """ Create a wave file from a existing .bas file diff --git a/PyDC/PyDC_cli.py b/PyDC/PyDC_cli.py index 9b374cb9..169f7062 100755 --- a/PyDC/PyDC_cli.py +++ b/PyDC/PyDC_cli.py @@ -13,7 +13,8 @@ import os import sys -from PyDC import TITLE_LINE, VERSION_STRING, wav2bas, bas2wav, analyze, bas2cas +from PyDC import TITLE_LINE, VERSION_STRING, wav2bas, bas2wav, analyze, bas2cas, \ + cas2bas from PyDC.base_cli import Base_CLI from PyDC.configs import Dragon32Config @@ -124,6 +125,9 @@ def run(self): elif source_ext.startswith(".bas") and dest_ext.startswith(".cas"): bas2cas(self.source_file, self.destination_file, self.cfg) + elif source_ext.startswith(".cas") and dest_ext.startswith(".bas"): + cas2bas(self.source_file, self.destination_file, self.cfg) + else: print "ERROR:" print "%s to %s ???" % (repr(self.source_file), repr(self.destination_file)) From d11a122515756717687a38aef8eb4a079702613e Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Thu, 5 Sep 2013 22:38:46 +0200 Subject: [PATCH 062/151] update cas2bas --- PyDC/PyDC/CassetteObjects.py | 79 ++++-------------- PyDC/PyDC/__init__.py | 53 ++++++++---- PyDC/PyDC/bitstream_handler.py | 143 +++++++++++++++++++++++---------- PyDC/PyDC/utils.py | 35 ++++---- 4 files changed, 173 insertions(+), 137 deletions(-) diff --git a/PyDC/PyDC/CassetteObjects.py b/PyDC/PyDC/CassetteObjects.py index 5755947d..ad000b23 100644 --- a/PyDC/PyDC/CassetteObjects.py +++ b/PyDC/PyDC/CassetteObjects.py @@ -20,6 +20,10 @@ from configs import Dragon32Config from utils import get_word, codepoints2string, string2codepoint, LOG_LEVEL_DICT, \ LOG_FORMATTER, codepoints2bitstream, pformat_codepoints +import functools +import itertools +from PyDC.utils import count_the_same + @@ -419,55 +423,6 @@ def add_from_bas(self, filename): self.current_file.create_from_bas(filename, file_content) self.files.append(self.current_file) - def add_from_cas(self, source_filepath): - log.debug("Open %s..." % repr(source_filepath)) - - class CasStream(object): - def __init__(self, source_filepath): - self.source_filepath = source_filepath - self.f = open(source_filepath, "rb") - self.pos = 0 - - def __iter__(self): - return self - def next(self): - self.last_byte = self.f.read(1) - if self.last_byte == "": - raise StopIteration - self.pos += 1 - self.last_codepoint = ord(self.last_byte) - return (self.pos, self.last_codepoint) - - cas_stream = CasStream(source_filepath) - - leadin_count = None -# for i in iter(cas_stream.next, self.cfg.LEAD_BYTE_CODEPOINT): -# print i -# break - - log.debug("Count leading byte.") -# for leadin_count, leadin_byte in iter(cas_stream.next, self.cfg.LEAD_BYTE_CODEPOINT): - for leadin_count, leadin_byte in cas_stream: - if leadin_byte != self.cfg.LEAD_BYTE_CODEPOINT: - break - - if leadin_count is None: - log.error("Leadin byte not found in file!") - sys.exit(-1) - log.info("%s x leadin bytes found." % leadin_count) - -# sync_byte = cas_stream.next() - sync_byte = cas_stream.last_codepoint - if sync_byte != self.cfg.SYNC_BYTE_CODEPOINT: - log.error("Sync byte wrong. Get %s but excepted %s" % ( - repr(sync_byte), hex(self.cfg.SYNC_BYTE_CODEPOINT) - )) - else: - log.debug("Sync byte, ok.") - - raise TODO - - def add_block(self, block_type, block_length, block_codepoints): if block_type == self.cfg.EOF_BLOCK: return @@ -628,19 +583,19 @@ def pprint_codepoint_stream(self): import subprocess -# # bas -> wav -# subprocess.Popen([sys.executable, "../PyDC_cli.py", -# "--verbosity=10", -# # "--verbosity=5", -# # "--logfile=5", -# # "--log_format=%(module)s %(lineno)d: %(message)s", -# # "../test_files/HelloWorld1.bas", "--dst=../test.wav" -# "../test_files/HelloWorld1.bas", "--dst=../test.cas" -# ]).wait() -# -# print "\n"*3 -# print "="*79 -# print "\n"*3 + # bas -> wav + subprocess.Popen([sys.executable, "../PyDC_cli.py", + "--verbosity=10", +# "--verbosity=5", +# "--logfile=5", +# "--log_format=%(module)s %(lineno)d: %(message)s", +# "../test_files/HelloWorld1.bas", "--dst=../test.wav" + "../test_files/HelloWorld1.bas", "--dst=../test.cas" + ]).wait() + + print "\n"*3 + print "="*79 + print "\n"*3 # # wav -> bas subprocess.Popen([sys.executable, "../PyDC_cli.py", diff --git a/PyDC/PyDC/__init__.py b/PyDC/PyDC/__init__.py index 2cd9c155..1d3f8e62 100644 --- a/PyDC/PyDC/__init__.py +++ b/PyDC/PyDC/__init__.py @@ -10,9 +10,10 @@ """ from CassetteObjects import Cassette -from bitstream_handler import BitstreamHandler +from bitstream_handler import BitstreamHandler, CasStream, BytestreamHandler from utils import print_bitlist from wave2bitstream import Wave2Bitstream, Bitstream2Wave +import sys __version__ = (0, 1, 0, 'dev') @@ -39,10 +40,17 @@ def cas2bas(source_filepath, destination_filepath, cfg): """ Read .cas file and create a .bas file """ - c = Cassette(cfg) - c.add_from_cas(source_filepath) - c.print_debug_info() - c.save_bas(destination_filepath) + cas_stream = CasStream(source_filepath) + bh = BytestreamHandler(cfg) + bh.feed(cas_stream) + + # save .bas file + bh.cassette.save_bas(destination_filepath) + +# c = Cassette(cfg) +# c.add_from_cas(source_filepath) +# c.print_debug_info() +# c.save_bas(destination_filepath) def bas2wav(source_filepath, destination_filepath, cfg): @@ -84,20 +92,31 @@ def wav2bas(source_filepath, destination_filepath, cfg): # verbose=False # # verbose=True # ) - print TITLE_LINE - - # test via CLI: - - import sys, subprocess - subprocess.Popen([sys.executable, "../PyDC_cli.py", "--verbosity=10", - # bas -> wav - "../test_files/HelloWorld1.bas", "../test.wav" +# sys.exit() + + import subprocess + + # bas -> wav + subprocess.Popen([sys.executable, "../PyDC_cli.py", + "--verbosity=10", +# "--verbosity=5", +# "--logfile=5", +# "--log_format=%(module)s %(lineno)d: %(message)s", +# "../test_files/HelloWorld1.bas", "--dst=../test.wav" + "../test_files/HelloWorld1.bas", "--dst=../test.cas" ]).wait() - subprocess.Popen([sys.executable, "../PyDC_cli.py", "--verbosity=10", - # wav -> bas -# "../test.wav", "../test.bas", - "../test_files/HelloWorld1 origin.wav", "../test_files/HelloWorld1.bas", + print "\n"*3 + print "="*79 + print "\n"*3 + +# # wav -> bas + subprocess.Popen([sys.executable, "../PyDC_cli.py", +# "--verbosity=10", + "--verbosity=7", +# "../test.wav", "--dst=../test.bas", + "../test.cas", "--dst=../test.bas", +# "../test_files/HelloWorld1 origin.wav", "--dst=../test_files/HelloWorld1.bas", ]).wait() print "-- END --" diff --git a/PyDC/PyDC/bitstream_handler.py b/PyDC/PyDC/bitstream_handler.py index 9542a568..3b755db2 100755 --- a/PyDC/PyDC/bitstream_handler.py +++ b/PyDC/PyDC/bitstream_handler.py @@ -21,6 +21,8 @@ # own modules from configs import Dragon32Config from CassetteObjects import Cassette +from PyDC.utils import count_the_same +import functools log = logging.getLogger("PyDC") @@ -91,7 +93,7 @@ def print_as_hex(block_codepoints): -class BitstreamHandler(object): +class BitstreamHandlerBase(object): def __init__(self, cfg): self.cfg = cfg self.cassette = Cassette(cfg) @@ -104,12 +106,14 @@ def feed(self, bitstream): # bitstream = iter(bitstream) try: - block_type, block_length, codepoints = self.get_block_info(bitstream) + self.sync_bitstream(bitstream) # Sync bitstream with SYNC_BYTE except SyncByteNotFoundError, err: log.error(err) log.info("Last wave pos: %s" % bitstream.pformat_pos()) break + block_type, block_length, codepoints = self.get_block_info(bitstream) + try: block_type_name = self.cfg.BLOCK_TYPE_DICT[block_type] except KeyError: @@ -138,6 +142,59 @@ def feed(self, bitstream): self.cassette.add_block(block_type, block_length, codepoints) print "="*79 + def get_block_info(self, codepoint_stream): + block_type = next(codepoint_stream) + log.info("raw block type: %s (%s)" % (hex(block_type), repr(block_type))) + + block_length = next(codepoint_stream) + + # Get the complete block content + codepoints = list(itertools.islice(codepoint_stream, block_length)) + + real_block_len = len(codepoints) + if real_block_len == block_length: + log.info("Block length: %sBytes, ok." % block_length) + else: + log.error("Block should be %sBytes but are: %sBytes!" % (block_length, real_block_len)) + + # Check block checksum + + origin_checksum = next(codepoint_stream) + + calc_checksum = sum([codepoint for codepoint in codepoints]) + calc_checksum += block_type + calc_checksum += block_length + calc_checksum = calc_checksum & 0xFF + + if calc_checksum == origin_checksum: + log.info("Block checksum %s is ok." % hex(origin_checksum)) + else: + log.error("Block checksum %s is not equal with calculated checksum: %s" % ( + hex(origin_checksum), hex(calc_checksum) + )) + + # Check if the magic byte exists + + magic_byte = next(codepoint_stream) + if magic_byte != self.cfg.MAGIC_BYTE: + log.error("Magic Byte %s is not %s" % (hex(magic_byte), hex(self.cfg.MAGIC_BYTE))) + else: + log.info("Magic Byte %s, ok." % hex(magic_byte)) + + return block_type, block_length, codepoints + + + +class BitstreamHandler(BitstreamHandlerBase): + """ + feed with wave bitstream + """ + def get_block_info(self, bitstream): + # convert the raw bitstream to codepoint stream + codepoint_stream = bitstream2codepoints(bitstream) + + return super(BitstreamHandler, self).get_block_info(codepoint_stream) + def sync_bitstream(self, bitstream): log.debug("start sync bitstream at wave pos: %s" % bitstream.pformat_pos()) bitstream.sync(32) # Sync bitstream to wave sinus cycle @@ -195,55 +252,59 @@ def sync_bitstream(self, bitstream): )) - def get_block_info(self, bitstream): - self.sync_bitstream(bitstream) # Sync bitstream with SYNC_BYTE - - # convert the raw bitstream to codepoint stream - codepoint_stream = bitstream2codepoints(bitstream) +class CasStream(object): + def __init__(self, source_filepath): + self.source_filepath = source_filepath + self.stat = os.stat(source_filepath) + self.file_size = self.stat.st_size + log.debug("file sizes: %s Bytes" % self.file_size) + self.pos = 0 + self.file_generator = self.__file_generator() - block_type = next(codepoint_stream) - log.info("raw block type: %s (%s)" % (hex(block_type), repr(block_type))) + self.yield_ord = True - block_length = next(codepoint_stream) + def __iter__(self): + return self - # Get the complete block content - codepoints = list(itertools.islice(codepoint_stream, block_length)) - - real_block_len = len(codepoints) - if real_block_len == block_length: - log.info("Block length: %sBytes, ok." % block_length) + def next(self): + byte = self.file_generator.next() + if self.yield_ord: + return ord(byte) else: - log.error("Block should be %sBytes but are: %sBytes!" % (block_length, real_block_len)) + return byte - # Check block checksum + def __file_generator(self): + max = self.file_size + 1 + with open(self.source_filepath, "rb") as f: + for chunk in iter(functools.partial(f.read, 1024), ""): + for byte in chunk: + self.pos += 1 + assert self.pos < max + yield byte - origin_checksum = next(codepoint_stream) + def get_ord(self): + byte = self.next() + codepoint = ord(byte) + return codepoint - calc_checksum = sum([codepoint for codepoint in codepoints]) - calc_checksum += block_type - calc_checksum += block_length - calc_checksum = calc_checksum & 0xFF - if calc_checksum == origin_checksum: - log.info("Block checksum %s is ok." % hex(origin_checksum)) - else: - log.error("Block checksum %s is not equal with calculated checksum: %s" % ( - hex(origin_checksum), hex(calc_checksum) +class BytestreamHandler(BitstreamHandlerBase): + """ + feed with byte stream e.g.: from cas file + """ + def sync_bitstream(self, bitstream): + leadin_bytes_count, sync_byte = count_the_same(bitstream, self.cfg.LEAD_BYTE_CODEPOINT) + if leadin_bytes_count == 0: + log.error("Leadin byte not found in file!") + sys.exit(-1) + log.info("%s x leadin bytes (%s) found." % (leadin_bytes_count, hex(self.cfg.LEAD_BYTE_CODEPOINT))) + + if sync_byte != self.cfg.SYNC_BYTE_CODEPOINT: + log.error("Sync byte wrong. Get %s but excepted %s" % ( + hex(sync_byte), hex(self.cfg.SYNC_BYTE_CODEPOINT) )) - - # Check if the magic byte exists - - magic_byte = next(codepoint_stream) - if magic_byte != self.cfg.MAGIC_BYTE: - log.error("Magic Byte %s is not %s" % (hex(magic_byte), hex(self.cfg.MAGIC_BYTE))) else: - log.info("Magic Byte %s, ok." % hex(magic_byte)) - - return block_type, block_length, codepoints - - - - + log.debug("Sync %s byte, ok." % hex(self.cfg.SYNC_BYTE_CODEPOINT)) diff --git a/PyDC/PyDC/utils.py b/PyDC/PyDC/utils.py index 57354546..8c88957a 100755 --- a/PyDC/PyDC/utils.py +++ b/PyDC/PyDC/utils.py @@ -16,7 +16,7 @@ import math -LOG_FORMATTER = logging.Formatter("") # %(asctime)s %(message)s") +LOG_FORMATTER = logging.Formatter("") # %(asctime)s %(message)s") LOG_LEVEL_DICT = { 0: logging.ERROR, 1: logging.WARNING, @@ -249,20 +249,21 @@ def find_iter_window(bitstream, pattern, max_pos=None): raise MaxPosArraived(pos) raise PatternNotFound(pos) -# def match_count(g, pattern): -# """ -# >>> match_count([, pattern) -# """ -# # Searching for lead-in byte -# leader_pos = find_iter_window(bit_list, LEAD_IN_PATTERN) # Search for LEAD_IN_PATTERN in bit-by-bit steps -# print "Start leader '%s' found at position: %i" % (LEAD_IN_PATTERN, leader_pos) -# -# # Cut bits before the first 01010101 start leader -# print "bits before header:", repr(int_list2str(bit_list[:leader_pos])) -# bit_list = bit_list[leader_pos:] -# -# # count lead-in byte matches without ceasing to get faster to the sync-byte -# leader_count = count_continuous_pattern(bit_list, LEAD_IN_PATTERN) + +def count_the_same(iterable, sentinel): + """ + >>> count_the_same([0x55,0x55,0x55,0x55,0x3C,"foo","bar"],0x55) + (4, 60) + >>> 0x3C == 60 + True + """ + count = 0 + x = None + for count, x in enumerate(iterable): + if x != sentinel: + break + return count, x + def diff_info(data): """ @@ -533,7 +534,7 @@ def print_codepoint_stream(codepoint_stream, display_block_count=8, no_repr=Fals line = [] for no, codepoint in enumerate(codepoint_stream, 1): r = repr(chr(codepoint)) - if "\\x" in r: # FIXME + if "\\x" in r: # FIXME txt = "%s %i" % (hex(codepoint), codepoint) else: txt = "%s %s" % (hex(codepoint), r) @@ -598,7 +599,7 @@ def print_line(no, line, line_info): line = [] for codepoint in line_info: r = repr(chr(codepoint)) - if "\\x" in r: # FIXME + if "\\x" in r: # FIXME txt = "%s" % hex(codepoint) else: txt = "%s %s" % (hex(codepoint), r) From bd267524cf253b5a1c3bd5918d19e53044917639 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Fri, 6 Sep 2013 08:52:20 +0200 Subject: [PATCH 063/151] Bugfix for wrong checksum --- PyDC/PyDC/CassetteObjects.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/PyDC/PyDC/CassetteObjects.py b/PyDC/PyDC/CassetteObjects.py index ad000b23..021ea902 100644 --- a/PyDC/PyDC/CassetteObjects.py +++ b/PyDC/PyDC/CassetteObjects.py @@ -475,12 +475,11 @@ def block2codepoint_stream(self, block_type, block_codepoints): log.debug("-"*79) log.debug(pformat_codepoints(block_codepoints)) log.debug("-"*79) - checksum = 0x00 - yield block_codepoints -# for codepoint in block_codepoints: -# checksum += codepoint -# yield codepoint + codepoints = tuple(block_codepoints) + yield codepoints + + checksum = sum([codepoint for codepoint in codepoints]) checksum += block_type checksum += block_length checksum = checksum & 0xFF From 1b9ab343f027dece3f9601763b9094d7df79f386 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Fri, 6 Sep 2013 08:52:44 +0200 Subject: [PATCH 064/151] debug block content --- PyDC/PyDC/bitstream_handler.py | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/PyDC/PyDC/bitstream_handler.py b/PyDC/PyDC/bitstream_handler.py index 3b755db2..e1b79c18 100755 --- a/PyDC/PyDC/bitstream_handler.py +++ b/PyDC/PyDC/bitstream_handler.py @@ -17,6 +17,7 @@ import itertools import logging import os +import sys # own modules from configs import Dragon32Config @@ -149,7 +150,12 @@ def get_block_info(self, codepoint_stream): block_length = next(codepoint_stream) # Get the complete block content - codepoints = list(itertools.islice(codepoint_stream, block_length)) + codepoints = tuple(itertools.islice(codepoint_stream, block_length)) + + log.debug("content of '%s':" % self.cfg.BLOCK_TYPE_DICT[block_type]) + log.debug("-"*79) + log.debug(pformat_codepoints(codepoints)) + log.debug("-"*79) real_block_len = len(codepoints) if real_block_len == block_length: @@ -336,19 +342,29 @@ def print_bit_list_stats(bit_list): # test via CLI: - import subprocess + import sys, subprocess # bas -> wav - subprocess.Popen([sys.executable, "../PyDC_cli.py", "--verbosity=10", + subprocess.Popen([sys.executable, "../PyDC_cli.py", + "--verbosity=10", +# "--verbosity=5", +# "--logfile=5", # "--log_format=%(module)s %(lineno)d: %(message)s", - "../test_files/HelloWorld1.bas", "../test.wav" +# "../test_files/HelloWorld1.bas", "--dst=../test.wav" + "../test_files/HelloWorld1.bas", "--dst=../test.cas" ]).wait() - # wav -> bas - subprocess.Popen([sys.executable, "../PyDC_cli.py", "--verbosity=10", -# "--log_format=%(module)s %(lineno)d: %(message)s", -# "../test.wav", "../test.bas", - "../test_files/HelloWorld1 origin.wav", "../test_files/HelloWorld1.bas", + print "\n"*3 + print "="*79 + print "\n"*3 + +# # wav -> bas + subprocess.Popen([sys.executable, "../PyDC_cli.py", +# "--verbosity=10", + "--verbosity=7", +# "../test.wav", "--dst=../test.bas", + "../test.cas", "--dst=../test.bas", +# "../test_files/HelloWorld1 origin.wav", "--dst=../test_files/HelloWorld1.bas", ]).wait() print "-- END --" From 148718198488266f7fc2568a123f79ac65b53971 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Fri, 6 Sep 2013 09:15:38 +0200 Subject: [PATCH 065/151] catch BASIC code end --- PyDC/PyDC/CassetteObjects.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/PyDC/PyDC/CassetteObjects.py b/PyDC/PyDC/CassetteObjects.py index 021ea902..e8855b52 100644 --- a/PyDC/PyDC/CassetteObjects.py +++ b/PyDC/PyDC/CassetteObjects.py @@ -220,12 +220,21 @@ def add_ascii_block(self, block_length, data): """ data = iter(data) + basic_code_end = "".join([chr(i) for i in self.cfg.BASIC_CODE_END]) + data.next() # Skip first \r byte_count = 1 # incl. first \r while True: code = iter(data.next, 0xd) # until \r code = "".join([chr(c) for c in code]) + if not code: + log.warning("code ended.") + break + + if code == basic_code_end: + log.debug("BASIC code end marker %s found." % pformat_codepoints(self.cfg.BASIC_CODE_END)) + byte_count += len(code) break byte_count += len(code) + 1 # and \r consumed in iter() From f8a7a78e34cd32e3ff3058e1e26e13f11c17c88c Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Fri, 6 Sep 2013 09:40:27 +0200 Subject: [PATCH 066/151] cleanup imports --- PyDC/PyDC/CassetteObjects.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/PyDC/PyDC/CassetteObjects.py b/PyDC/PyDC/CassetteObjects.py index e8855b52..9299a560 100644 --- a/PyDC/PyDC/CassetteObjects.py +++ b/PyDC/PyDC/CassetteObjects.py @@ -19,10 +19,7 @@ from basic_tokens import bytes2codeline from configs import Dragon32Config from utils import get_word, codepoints2string, string2codepoint, LOG_LEVEL_DICT, \ - LOG_FORMATTER, codepoints2bitstream, pformat_codepoints -import functools -import itertools -from PyDC.utils import count_the_same + LOG_FORMATTER, pformat_codepoints From e1ad4fdb12854a1fe03b4edb955b7b385ab67c05 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Fri, 6 Sep 2013 09:41:16 +0200 Subject: [PATCH 067/151] add unittests for bas2cas -> cas2bas --- PyDC/PyDC/tests.py | 58 ++++++++++++++++++++++++++++-- PyDC/test_files/LineNumberTest.bas | 3 +- 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/PyDC/PyDC/tests.py b/PyDC/PyDC/tests.py index fe801a99..6e7ee706 100755 --- a/PyDC/PyDC/tests.py +++ b/PyDC/PyDC/tests.py @@ -16,7 +16,7 @@ # own modules import configs -from __init__ import wav2bas +from __init__ import wav2bas, bas2cas, cas2bas from wave2bitstream import Wave2Bitstream @@ -44,11 +44,12 @@ def _dst_file_path(self, filename): os.path.join(self.base_path, filename) ) - def _get_and_delete_dst(self, destination_filepath): + def _get_and_delete_dst(self, destination_filepath, delete=True): f = open(destination_filepath, "r") dest_content = f.read() f.close() - os.remove(destination_filepath) + if delete: + os.remove(destination_filepath) return dest_content def test_wav2bas01(self): @@ -131,6 +132,56 @@ def test_statistics(self): statistics, {10: 17, 11: 44, 12: 4, 19: 5, 20: 44, 21: 15} ) +# def test_cas01(self): +# # create cas +# source_filepath = self._src_file_path("HelloWorld1.bas") +# destination_filepath = self._dst_file_path("unittest_HelloWorld1.cas") +# +# cfg = configs.Dragon32Config() +# cfg.LEAD_BYTE_LEN = 3 +# bas2cas(source_filepath, destination_filepath, cfg) +# +# dest_content = self._get_and_delete_dst(destination_filepath) +# +# print repr(dest_content) +# +# 'UUU<\x00\x0fHELLOWOR\x00\xff\x00\x0c\x00\x0c\x00\x92UUUU<\x01:\r10 FOR I = 1 TO 10\r20 PRINT I;"HELLO WORLD!"\r30 NEXT I\r\x00\x00\x91UUUU<\xff\x00\xffU' +# +# self.assertMultiLineEqual(dest_content, ( +# '1 PRINT "LINE NUMBER TEST"\n' +# '10 PRINT 10\n' +# '100 PRINT 100\n' +# '1000 PRINT 1000\n' +# '10000 PRINT 10000\n' +# '32768 PRINT 32768\n' +# '63999 PRINT "END";63999\n' +# )) + + def test_cas02(self): + # create cas + source_filepath = self._src_file_path("LineNumberTest.bas") + cas_filepath = self._dst_file_path("unittest_LineNumberTest.cas") + bas2cas(source_filepath, cas_filepath, self.cfg) + + # create bas from created cas file + destination_filepath = self._dst_file_path("unittest_LineNumberTest.bas") + cas2bas(cas_filepath, destination_filepath, self.cfg) + + destination_filepath = self._dst_file_path("unittest_LineNumberTest_LINENUMB.bas") + dest_content = self._get_and_delete_dst(destination_filepath) + + self.assertMultiLineEqual(dest_content, ( + '1 PRINT "LINE NUMBER TEST"\n' + '10 PRINT 10\n' + '100 PRINT 100\n' + '1000 PRINT 1000\n' + '10000 PRINT 10000\n' + '32768 PRINT 32768\n' + '63999 PRINT "END";63999\n' + )) + + os.remove(cas_filepath) + # def test_bas2ascii_wav(self): # pass # TODO @@ -151,6 +202,7 @@ def test_statistics(self): # "TestDragon32Conversion.test_wav2bas01", # "TestDragon32Conversion.test_wav2bas04", # "TestDragon32Conversion.test_statistics", +# "TestDragon32Conversion.test_cas01", # "TestDragon32Conversion.test_bas2ascii_wav", # TODO ), # verbosity=1, diff --git a/PyDC/test_files/LineNumberTest.bas b/PyDC/test_files/LineNumberTest.bas index 60755ae2..8c385b5a 100755 --- a/PyDC/test_files/LineNumberTest.bas +++ b/PyDC/test_files/LineNumberTest.bas @@ -1,6 +1,7 @@ 1 PRINT "LINE NUMBER TEST" 10 PRINT 10 100 PRINT 100 -1000 PRINT 10000 +1000 PRINT 1000 +10000 PRINT 10000 32768 PRINT 32768 63999 PRINT "END";63999 \ No newline at end of file From e7a1afdac9017ac7247b0147109eaa72832a45cc Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Fri, 6 Sep 2013 10:12:23 +0200 Subject: [PATCH 068/151] add a "step-by-step" bas2cas unittest --- PyDC/PyDC/tests.py | 93 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 65 insertions(+), 28 deletions(-) diff --git a/PyDC/PyDC/tests.py b/PyDC/PyDC/tests.py index 6e7ee706..e479bf3a 100755 --- a/PyDC/PyDC/tests.py +++ b/PyDC/PyDC/tests.py @@ -18,6 +18,7 @@ import configs from __init__ import wav2bas, bas2cas, cas2bas from wave2bitstream import Wave2Bitstream +import itertools class TestDragon32Conversion(unittest.TestCase): @@ -132,32 +133,67 @@ def test_statistics(self): statistics, {10: 17, 11: 44, 12: 4, 19: 5, 20: 44, 21: 15} ) -# def test_cas01(self): -# # create cas -# source_filepath = self._src_file_path("HelloWorld1.bas") -# destination_filepath = self._dst_file_path("unittest_HelloWorld1.cas") -# -# cfg = configs.Dragon32Config() -# cfg.LEAD_BYTE_LEN = 3 -# bas2cas(source_filepath, destination_filepath, cfg) -# -# dest_content = self._get_and_delete_dst(destination_filepath) -# + def test_bas2cas01(self): + # create cas + source_filepath = self._src_file_path("HelloWorld1.bas") + destination_filepath = self._dst_file_path("unittest_HelloWorld1.cas") + + cfg = configs.Dragon32Config() + cfg.LEAD_BYTE_LEN = 35 + bas2cas(source_filepath, destination_filepath, cfg) + + dest_content = self._get_and_delete_dst(destination_filepath) # print repr(dest_content) -# -# 'UUU<\x00\x0fHELLOWOR\x00\xff\x00\x0c\x00\x0c\x00\x92UUUU<\x01:\r10 FOR I = 1 TO 10\r20 PRINT I;"HELLO WORLD!"\r30 NEXT I\r\x00\x00\x91UUUU<\xff\x00\xffU' -# -# self.assertMultiLineEqual(dest_content, ( -# '1 PRINT "LINE NUMBER TEST"\n' -# '10 PRINT 10\n' -# '100 PRINT 100\n' -# '1000 PRINT 1000\n' -# '10000 PRINT 10000\n' -# '32768 PRINT 32768\n' -# '63999 PRINT "END";63999\n' -# )) - - def test_cas02(self): + + cas = ( + ("UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU", "35x Leadin bytes 0x55"), + ("<", "Sync byte 0x3C"), + + ("\x00", "block type: filename block (0x00)"), + ("\x0f", "block length (15Bytes)"), + + ("HELLOWOR", "filename"), + ("\x00", "File type: BASIC programm (0x00)"), + ("\xff", "format: ASCII BASIC (0xff)"), + ("\x00", "gap flag (00=no gaps, FF=gaps)"), + ("\x0c\x00", "machine code starting address"), + ("\x0c\x00", "machine code loading address"), + + ("\x92", "block checksum"), + ("U", "magic byte block terminator 0x3C"), + + ("UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU", "35x Leadin bytes 0x55"), + ("<", "Sync byte 0x3C"), + + ("\x01", "block type: data block (0x01)"), + (":", "block length 0x3a (58Bytes)"), + ( + '\r10 FOR I = 1 TO 10\r20 PRINT I;"HELLO WORLD!"\r30 NEXT I\r', + "Basic code in ASCII format" + ), + ("\x00\x00", "code end terminator"), + ("\x91", "block checksum"), + ("U", "magic byte block terminator 0x3C"), + + ("UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU", "35x Leadin bytes 0x55"), + ("<", "Sync byte 0x3C"), + + ("\xff", "block type: end-of-file block (0xff)"), + ("\x00", "block length (0Bytes)"), + ("\xff", "block checksum"), + ("U", "magic byte block terminator 0x3C"), + ) + + dest_content = iter(dest_content) + for no, data in enumerate(cas): + part, desc = data + part_len = len(part) + dest_part = "".join(tuple(itertools.islice(dest_content, part_len))) + self.assertEqual(part, dest_part, + msg="Error in part %i '%s': %s != %s" % (no, desc, repr(part), repr(dest_part)) + ) + + def test_cas01(self): # create cas source_filepath = self._src_file_path("LineNumberTest.bas") cas_filepath = self._dst_file_path("unittest_LineNumberTest.cas") @@ -189,10 +225,10 @@ def test_cas02(self): if __name__ == '__main__': # log = logging.getLogger("PyDC") # log.setLevel( -# #~ logging.ERROR -# logging.INFO +# # logging.ERROR +# # logging.INFO # # logging.WARNING -# # logging.DEBUG +# logging.DEBUG # ) # log.addHandler(logging.StreamHandler()) @@ -202,6 +238,7 @@ def test_cas02(self): # "TestDragon32Conversion.test_wav2bas01", # "TestDragon32Conversion.test_wav2bas04", # "TestDragon32Conversion.test_statistics", +# "TestDragon32Conversion.test_bas2cas01", # "TestDragon32Conversion.test_cas01", # "TestDragon32Conversion.test_bas2ascii_wav", # TODO ), From 815b7d33fd1d12f2ed0146ac81f4e4dacbb76b83 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Fri, 6 Sep 2013 18:25:55 +0200 Subject: [PATCH 069/151] bas2wav works better, but some bugs not fixed, yet... --- PyDC/PyDC/CassetteObjects.py | 41 ++++++++------- PyDC/PyDC/tests.py | 35 ++++++++++--- PyDC/PyDC/wave2bitstream.py | 98 +++++++++++++++++++++--------------- 3 files changed, 109 insertions(+), 65 deletions(-) diff --git a/PyDC/PyDC/CassetteObjects.py b/PyDC/PyDC/CassetteObjects.py index 9299a560..dfd170f9 100644 --- a/PyDC/PyDC/CassetteObjects.py +++ b/PyDC/PyDC/CassetteObjects.py @@ -495,6 +495,9 @@ def block2codepoint_stream(self, block_type, block_codepoints): yield self.cfg.MAGIC_BYTE # 0x55 def codepoint_stream(self): + if self.wav: + self.wav.write_silence(sec=0.1) + for file_obj in self.files: # yield filename for codepoints in self.block2codepoint_stream( @@ -504,7 +507,7 @@ def codepoint_stream(self): yield codepoints if self.wav: - self.wav.write_silence(sec=2) + self.wav.write_silence(sec=0.1) # yield file content for codepoints in self.block2codepoint_stream( @@ -514,7 +517,7 @@ def codepoint_stream(self): yield codepoints if self.wav: - self.wav.write_silence(sec=2) + self.wav.write_silence(sec=0.1) # yield EOF for codepoints in self.block2codepoint_stream( @@ -524,7 +527,7 @@ def codepoint_stream(self): yield codepoints if self.wav: - self.wav.write_silence(sec=2) + self.wav.write_silence(sec=0.1) def write_wave(self, wav): self.wav = wav # Bitstream2Wave instance @@ -594,23 +597,23 @@ def pprint_codepoint_stream(self): # "--verbosity=5", # "--logfile=5", # "--log_format=%(module)s %(lineno)d: %(message)s", -# "../test_files/HelloWorld1.bas", "--dst=../test.wav" - "../test_files/HelloWorld1.bas", "--dst=../test.cas" - ]).wait() - - print "\n"*3 - print "="*79 - print "\n"*3 - -# # wav -> bas - subprocess.Popen([sys.executable, "../PyDC_cli.py", -# "--verbosity=10", - "--verbosity=7", -# "../test.wav", "--dst=../test.bas", - "../test.cas", "--dst=../test.bas", -# "../test_files/HelloWorld1 origin.wav", "--dst=../test_files/HelloWorld1.bas", + "../test_files/HelloWorld1.bas", "--dst=../test.wav" +# "../test_files/HelloWorld1.bas", "--dst=../test.cas" ]).wait() - print "-- END --" +# print "\n"*3 +# print "="*79 +# print "\n"*3 +# +# # # wav -> bas +# subprocess.Popen([sys.executable, "../PyDC_cli.py", +# # "--verbosity=10", +# "--verbosity=7", +# # "../test.wav", "--dst=../test.bas", +# "../test.cas", "--dst=../test.bas", +# # "../test_files/HelloWorld1 origin.wav", "--dst=../test_files/HelloWorld1.bas", +# ]).wait() +# +# print "-- END --" diff --git a/PyDC/PyDC/tests.py b/PyDC/PyDC/tests.py index e479bf3a..8269b00a 100755 --- a/PyDC/PyDC/tests.py +++ b/PyDC/PyDC/tests.py @@ -13,12 +13,12 @@ import os import sys import unittest +import itertools # own modules +from __init__ import wav2bas, bas2cas, cas2bas, bas2wav import configs -from __init__ import wav2bas, bas2cas, cas2bas from wave2bitstream import Wave2Bitstream -import itertools class TestDragon32Conversion(unittest.TestCase): @@ -203,6 +203,9 @@ def test_cas01(self): destination_filepath = self._dst_file_path("unittest_LineNumberTest.bas") cas2bas(cas_filepath, destination_filepath, self.cfg) + os.remove(cas_filepath) + + # filename 'LINENUMB' used in CSAVE: destination_filepath = self._dst_file_path("unittest_LineNumberTest_LINENUMB.bas") dest_content = self._get_and_delete_dst(destination_filepath) @@ -216,10 +219,30 @@ def test_cas01(self): '63999 PRINT "END";63999\n' )) - os.remove(cas_filepath) + def test_bas2ascii_wav(self): + # create wav + source_filepath = self._src_file_path("HelloWorld1.bas") + destination_filepath = self._dst_file_path("unittest_HelloWorld1.wav") + + cfg = configs.Dragon32Config() + cfg.LEAD_BYTE_LEN = 128 + bas2wav(source_filepath, destination_filepath, cfg) + + # read wave and compare + source_filepath = self._dst_file_path("unittest_HelloWorld1.wav") + destination_filepath = self._dst_file_path("unittest_bas2ascii_wav.bas") + wav2bas(source_filepath, destination_filepath, self.cfg) + + # filename 'HELLOWOR' used in CSAVE: + destination_filepath = self._dst_file_path("unittest_bas2ascii_wav_HELLOWOR.bas") -# def test_bas2ascii_wav(self): -# pass # TODO + dest_content = self._get_and_delete_dst(destination_filepath) + + self.assertEqual(dest_content, ( + '10 FOR I = 1 TO 10\n' + '20 PRINT I;"HELLO WORLD!"\n' + '30 NEXT I\n' + )) if __name__ == '__main__': @@ -240,7 +263,7 @@ def test_cas01(self): # "TestDragon32Conversion.test_statistics", # "TestDragon32Conversion.test_bas2cas01", # "TestDragon32Conversion.test_cas01", -# "TestDragon32Conversion.test_bas2ascii_wav", # TODO +# "TestDragon32Conversion.test_bas2ascii_wav", ), # verbosity=1, verbosity=2, diff --git a/PyDC/PyDC/wave2bitstream.py b/PyDC/PyDC/wave2bitstream.py index f9289acd..3797b3f7 100644 --- a/PyDC/PyDC/wave2bitstream.py +++ b/PyDC/PyDC/wave2bitstream.py @@ -66,7 +66,7 @@ def get_typecode(self, samplewidth): return typecode def pformat_pos(self): - sec = float(self.wave_pos) / self.framerate + sec = float(self.wave_pos) / self.framerate / self.samplewidth return "%s (frame no.: %s)" % (human_duration(sec), self.wave_pos) def _hz2duration(self, hz): @@ -75,6 +75,20 @@ def _hz2duration(self, hz): def _duration2hz(self, duration): return duration2hz(duration, framerate=self.framerate) + def set_wave_properties(self): + self.framerate = self.wavefile.getframerate() # frames / second + self.samplewidth = self.wavefile.getsampwidth() # 1 for 8-bit, 2 for 16-bit, 4 for 32-bit samples + self.max_value = MAX_VALUES[self.samplewidth] + self.nchannels = self.wavefile.getnchannels() # typically 1 for mono, 2 for stereo + + log.info("Framerate: %sHz samplewidth: %i (%sBit, max volume value: %s) channels: %s" % ( + self.framerate, + self.samplewidth, self.samplewidth * 8, self.max_value, + self.nchannels, + )) + + assert self.nchannels == 1, "Only MONO files are supported, yet!" + class Wave2Bitstream(WaveBase): @@ -92,25 +106,16 @@ def __init__(self, wave_filename, cfg): try: self.wavefile = wave.open(wave_filename, "rb") except IOError, err: - log.error("Error opening %s: %s" % (repr(wave_filename), err)) + msg = "Error opening %s: %s" % (repr(wave_filename), err) + log.error(msg) + sys.stderr.write(msg) sys.exit(-1) - self.framerate = self.wavefile.getframerate() # frames / second - print "Framerate:", self.framerate + self.set_wave_properties() self.frame_count = self.wavefile.getnframes() print "Number of audio frames:", self.frame_count - self.nchannels = self.wavefile.getnchannels() # typically 1 for mono, 2 for stereo - print "channels:", self.nchannels - assert self.nchannels == 1, "Only MONO files are supported, yet!" - - self.samplewidth = self.wavefile.getsampwidth() # 1 for 8-bit, 2 for 16-bit, 4 for 32-bit samples - print "samplewidth: %i (%sBit wave file)" % (self.samplewidth, self.samplewidth * 8) - - self.max_value = MAX_VALUES[self.samplewidth] - print "the max volume value is:", self.max_value - self.min_volume = int(round(self.max_value * cfg.MIN_VOLUME_RATIO / 100)) print "Ignore sample lower than %.1f%% = %i" % (cfg.MIN_VOLUME_RATIO, self.min_volume) @@ -537,9 +542,20 @@ def __init__(self, destination_filepath, cfg): self.wavefile.setnchannels(1) # Mono self.wavefile.setsampwidth(self.cfg.SAMPLEWIDTH) self.wavefile.setframerate(self.cfg.FRAMERATE) - self.framerate = self.cfg.FRAMERATE - self.wave_pos = self.wavefile._nframeswritten + self.set_wave_properties() + + @property + def wave_pos(self): + pos = self.wavefile._nframeswritten * self.samplewidth + return pos + + def pack_values(self, values): + value_length = len(values) + pack_format = "%i%s" % (value_length, self.typecode) + packed_samples = struct.pack(pack_format, *values) + + return packed_samples def get_samples(self, hz): values = tuple( @@ -547,7 +563,8 @@ def get_samples(self, hz): ) real_hz = float(self.cfg.FRAMERATE) / len(values) log.debug("Real frequency: %.2f" % real_hz) - return array.array(self.typecode, values) + return self.pack_values(values) + def write_codepoint(self, codepoints): written_codepoints = [] @@ -569,19 +586,20 @@ def write_codepoint(self, codepoints): log.debug("Written at %s: %s" % ( self.pformat_pos(), ",".join([hex(x) for x in written_codepoints]) )) - self.wave_pos = self.wavefile._nframeswritten def write_silence(self, sec): - # the filename block was written - silence = [0x00 for _ in xrange(sec * self.framerate)] - silence = array.array(self.typecode, silence) - self.wavefile.writeframes(silence) - log.debug("Write %ssec. silence at %s" % (sec, self.pformat_pos())) - self.wave_pos = self.wavefile._nframeswritten + start_pos = self.pformat_pos() + silence = [0x00] * int(round((sec * self.framerate))) + + packed_samples = self.pack_values(silence) + + self.wavefile.writeframes(packed_samples) + log.debug("Write %ssec. silence %s - %s" % ( + sec, start_pos, self.pformat_pos() + )) def close(self): self.wavefile.close() - self.wave_pos = self.wavefile._nframeswritten log.info("Wave file %s written (%s)" % ( self.destination_filepath, self.pformat_pos() )) @@ -622,18 +640,18 @@ def close(self): "../test_files/HelloWorld1.bas", "--dst=../test.wav" ]).wait() - print "\n"*3 - print "="*79 - print "\n"*3 - - # wav -> bas - subprocess.Popen([sys.executable, "../PyDC_cli.py", - "--verbosity=10", -# "--verbosity=5", -# "--logfile=5", -# "--log_format=%(module)s %(lineno)d: %(message)s", - "../test.wav", "--dst=../test.bas", -# "../test_files/HelloWorld1 origin.wav", "--dst=../test_files/HelloWorld1.bas", - ]).wait() - - print "-- END --" +# print "\n"*3 +# print "="*79 +# print "\n"*3 +# +# # wav -> bas +# subprocess.Popen([sys.executable, "../PyDC_cli.py", +# "--verbosity=10", +# # "--verbosity=5", +# # "--logfile=5", +# # "--log_format=%(module)s %(lineno)d: %(message)s", +# "../test.wav", "--dst=../test.bas", +# # "../test_files/HelloWorld1 origin.wav", "--dst=../test_files/HelloWorld1.bas", +# ]).wait() +# +# print "-- END --" From f15379a91f3c9e48ea1433397b65c887e731b2a8 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Mon, 2 Sep 2013 17:07:47 +0200 Subject: [PATCH 070/151] use defaults in CLI from config --- PyDC/PyDC_cli.py | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/PyDC/PyDC_cli.py b/PyDC/PyDC_cli.py index 169f7062..7cce3f5b 100755 --- a/PyDC/PyDC_cli.py +++ b/PyDC/PyDC_cli.py @@ -31,6 +31,7 @@ class PyDC_CLI(Base_CLI): def __init__(self): super(PyDC_CLI, self).__init__() + self.cfg = Dragon32Config() self.parser.add_argument("src", help="Source filename (.wav/.bas)") self.parser.add_argument("--dst", @@ -46,36 +47,36 @@ def __init__(self): # For Wave2Bitstream(): self.parser.add_argument( - "--hz_variation", type=int, default=450, + "--hz_variation", type=int, default=self.cfg.HZ_VARIATION, help=( "How much Hz can signal scatter to match 1 or 0 bit ?" - " (default: 450)" - ) + " (default: %s)" + ) % self.cfg.HZ_VARIATION ) self.parser.add_argument( - "--min_volume_ratio", type=int, default=5, - help="percent volume to ignore sample (default: 5)" + "--min_volume_ratio", type=int, default=self.cfg.MIN_VOLUME_RATIO, + help="percent volume to ignore sample (default: %s)" % self.cfg.MIN_VOLUME_RATIO ) self.parser.add_argument( - "--avg_count", type=int, default=0, + "--avg_count", type=int, default=self.cfg.AVG_COUNT, help=( "How many samples should be merged into a average value?" - " (default: 0)" - ) + " (default: %s)" + ) % self.cfg.AVG_COUNT ) self.parser.add_argument( - "--end_count", type=int, default=2, + "--end_count", type=int, default=self.cfg.END_COUNT, help=( "Sample count that must be pos/neg at once" - " (default: 2)" - ) + " (default: %s)" + ) % self.cfg.END_COUNT ) self.parser.add_argument( - "--mid_count", type=int, default=1, + "--mid_count", type=int, default=self.cfg.MID_COUNT, help=( "Sample count that can be around null" - " (default: 1)" - ) + " (default: %s)" + ) % self.cfg.MID_COUNT ) def parse_args(self): @@ -107,8 +108,6 @@ def run(self): self.setup_logging(self.args) # XXX: setup logging after the logfilename is set! - self.cfg = Dragon32Config() - self.cfg.HZ_VARIATION = self.args.hz_variation # How much Hz can signal scatter to match 1 or 0 bit ? self.cfg.MIN_VOLUME_RATIO = self.args.min_volume_ratio # percent volume to ignore sample self.cfg.AVG_COUNT = self.args.avg_count # How many samples should be merged into a average value? From e5ef146c4c27bfa7f0d6cee703f1434915add3d8 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Fri, 6 Sep 2013 21:07:55 +0200 Subject: [PATCH 071/151] Add Frequency of bit 1 / 0 in cli --- PyDC/PyDC_cli.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/PyDC/PyDC_cli.py b/PyDC/PyDC_cli.py index 7cce3f5b..3a1cf1d7 100755 --- a/PyDC/PyDC_cli.py +++ b/PyDC/PyDC_cli.py @@ -46,6 +46,21 @@ def __init__(self): ) # For Wave2Bitstream(): + self.parser.add_argument( + "--bit_one_hz", type=int, default=self.cfg.BIT_ONE_HZ, + help=( + "Frequency of bit '1' in Hz" + " (default: %s)" + ) % self.cfg.BIT_ONE_HZ + ) + self.parser.add_argument( + "--bit_nul_hz", type=int, default=self.cfg.BIT_NUL_HZ, + help=( + "Frequency of bit '0' in Hz" + " (default: %s)" + ) % self.cfg.BIT_NUL_HZ + ) + self.parser.add_argument( "--hz_variation", type=int, default=self.cfg.HZ_VARIATION, help=( @@ -53,6 +68,7 @@ def __init__(self): " (default: %s)" ) % self.cfg.HZ_VARIATION ) + self.parser.add_argument( "--min_volume_ratio", type=int, default=self.cfg.MIN_VOLUME_RATIO, help="percent volume to ignore sample (default: %s)" % self.cfg.MIN_VOLUME_RATIO @@ -108,7 +124,10 @@ def run(self): self.setup_logging(self.args) # XXX: setup logging after the logfilename is set! + self.cfg.BIT_ONE_HZ = self.args.bit_one_hz # Frequency of bit '1' in Hz + self.cfg.BIT_NUL_HZ = self.args.bit_nul_hz # Frequency of bit '0' in Hz self.cfg.HZ_VARIATION = self.args.hz_variation # How much Hz can signal scatter to match 1 or 0 bit ? + self.cfg.MIN_VOLUME_RATIO = self.args.min_volume_ratio # percent volume to ignore sample self.cfg.AVG_COUNT = self.args.avg_count # How many samples should be merged into a average value? self.cfg.END_COUNT = self.args.end_count # Sample count that must be pos/neg at once From f7790e6bd83109ae0336cbd526e0750358b66e61 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Fri, 6 Sep 2013 21:08:36 +0200 Subject: [PATCH 072/151] bugfix if only bit 1 or bit 0 found. --- PyDC/PyDC/wave2bitstream.py | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/PyDC/PyDC/wave2bitstream.py b/PyDC/PyDC/wave2bitstream.py index f9289acd..b7b72520 100644 --- a/PyDC/PyDC/wave2bitstream.py +++ b/PyDC/PyDC/wave2bitstream.py @@ -273,17 +273,15 @@ def iter_bitstream(self, iter_duration_generator): assert bit_one_max_duration < bit_nul_min_duration, "HZ_VARIATION value is too high!" # for end statistics - one_hz_count = 0 + bit_one_count = 0 one_hz_min = sys.maxint one_hz_avg = None one_hz_max = 0 - nul_hz_count = 0 + bit_nul_count = 0 nul_hz_min = sys.maxint nul_hz_avg = None nul_hz_max = 0 - bit_count = 0 - for duration in iter_duration_generator: if bit_one_min_duration < duration < bit_one_max_duration: @@ -293,14 +291,13 @@ def iter_bitstream(self, iter_duration_generator): self.pformat_pos(), duration, hz ) ) - bit_count += 1 yield 1 - one_hz_count += 1 + bit_one_count += 1 if hz < one_hz_min: one_hz_min = hz if hz > one_hz_max: one_hz_max = hz - one_hz_avg = average(one_hz_avg, hz, one_hz_count) + one_hz_avg = average(one_hz_avg, hz, bit_one_count) elif bit_nul_min_duration < duration < bit_nul_max_duration: hz = self._duration2hz(duration) log.log(5, @@ -308,14 +305,13 @@ def iter_bitstream(self, iter_duration_generator): self.pformat_pos(), duration, hz ) ) - bit_count += 1 yield 0 - nul_hz_count += 1 + bit_nul_count += 1 if hz < nul_hz_min: nul_hz_min = hz if hz > nul_hz_max: nul_hz_max = hz - nul_hz_avg = average(nul_hz_avg, hz, nul_hz_count) + nul_hz_avg = average(nul_hz_avg, hz, bit_nul_count) else: hz = self._duration2hz(duration) log.log(7, @@ -325,21 +321,24 @@ def iter_bitstream(self, iter_duration_generator): ) continue + bit_count = bit_one_count + bit_nul_count + if bit_count == 0: print "ERROR: No information from wave to generate the bits" print "trigger volume to high?" sys.exit(-1) log.info("\n%i Bits: %i positive bits and %i negative bits" % ( - bit_count, one_hz_count, nul_hz_count + bit_count, bit_one_count, bit_nul_count )) - if bit_count > 0: - log.info("Bit 0: %sHz - %sHz avg: %.1fHz variation: %sHz" % ( - nul_hz_min, nul_hz_max, nul_hz_avg, nul_hz_max - nul_hz_min - )) + if bit_one_count > 0: log.info("Bit 1: %sHz - %sHz avg: %.1fHz variation: %sHz" % ( one_hz_min, one_hz_max, one_hz_avg, one_hz_max - one_hz_min )) + if bit_nul_count > 0: + log.info("Bit 0: %sHz - %sHz avg: %.1fHz variation: %sHz" % ( + nul_hz_min, nul_hz_max, nul_hz_avg, nul_hz_max - nul_hz_min + )) def iter_duration(self, iter_trigger): """ From d00f810cc11206d196e0f2a37c2464909cec070d Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Fri, 6 Sep 2013 21:20:41 +0200 Subject: [PATCH 073/151] Binary machine code file is 0x02 and not 0xff, isn't it? --- PyDC/PyDC/configs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PyDC/PyDC/configs.py b/PyDC/PyDC/configs.py index 53a3da21..44d6fa12 100644 --- a/PyDC/PyDC/configs.py +++ b/PyDC/PyDC/configs.py @@ -104,11 +104,11 @@ class Dragon32Config(BaseConfig): # File types: FTYPE_BASIC = 0x00 FTYPE_DATA = 0x01 - FTYPE_BIN = 0xff + FTYPE_BIN = 0x02 FILETYPE_DICT = { FTYPE_BASIC:"BASIC programm (0x00)", FTYPE_DATA:"Data file (0x01)", - FTYPE_BIN:"Binary file (0xFF)", + FTYPE_BIN:"Binary machine code file (0x02)", } # Basic format types: From ba8c425c809bebf3be776ef36e9d9b3542bffb8e Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Fri, 6 Sep 2013 21:27:37 +0200 Subject: [PATCH 074/151] log.info: gap flag / machine code starting/loading address --- PyDC/PyDC/CassetteObjects.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/PyDC/PyDC/CassetteObjects.py b/PyDC/PyDC/CassetteObjects.py index 9299a560..8d998781 100644 --- a/PyDC/PyDC/CassetteObjects.py +++ b/PyDC/PyDC/CassetteObjects.py @@ -312,8 +312,7 @@ def create_from_wave(self, codepoints): self.filename = codepoints2string(raw_filename).rstrip() print "\nFilename: %s" % repr(self.filename) -# print "file meta:" -# print_codepoint_stream(codepoints) + self.file_type = codepoints[8] @@ -340,6 +339,17 @@ def create_from_wave(self, codepoints): log.info("ASCII flag: %s" % self.cfg.BASIC_TYPE_DICT[ascii_flag]) + self.gap_flag = codepoints[10] + log.info("gap flag is %s (0x00=no gaps, 0xff=gaps)" % hex(self.gap_flag)) + + codepoints = iter(codepoints) + + self.start_address = get_word(codepoints) + log.info("machine code starting address: %s" % hex(self.start_address)) + + self.load_address = get_word(codepoints) + log.info("machine code loading address: %s" % hex(self.load_address)) + self.file_content = FileContent(self.cfg) def add_block_data(self, block_length, codepoints): From c30ae70ec82cf8b38f5e58ce21cefe455f9e0e59 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Fri, 6 Sep 2013 23:16:13 +0200 Subject: [PATCH 075/151] bugfix: .cas file must not have leadin bytes between blocks. --- PyDC/PyDC/bitstream_handler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PyDC/PyDC/bitstream_handler.py b/PyDC/PyDC/bitstream_handler.py index e1b79c18..5b77f5dd 100755 --- a/PyDC/PyDC/bitstream_handler.py +++ b/PyDC/PyDC/bitstream_handler.py @@ -302,8 +302,8 @@ def sync_bitstream(self, bitstream): leadin_bytes_count, sync_byte = count_the_same(bitstream, self.cfg.LEAD_BYTE_CODEPOINT) if leadin_bytes_count == 0: log.error("Leadin byte not found in file!") - sys.exit(-1) - log.info("%s x leadin bytes (%s) found." % (leadin_bytes_count, hex(self.cfg.LEAD_BYTE_CODEPOINT))) + else: + log.info("%s x leadin bytes (%s) found." % (leadin_bytes_count, hex(self.cfg.LEAD_BYTE_CODEPOINT))) if sync_byte != self.cfg.SYNC_BYTE_CODEPOINT: log.error("Sync byte wrong. Get %s but excepted %s" % ( From 9264ba1818b2fb03b1793554f23237909fa061bf Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Fri, 6 Sep 2013 23:16:48 +0200 Subject: [PATCH 076/151] display more info, if block type unknown. --- PyDC/PyDC/bitstream_handler.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/PyDC/PyDC/bitstream_handler.py b/PyDC/PyDC/bitstream_handler.py index 5b77f5dd..5bcdd50f 100755 --- a/PyDC/PyDC/bitstream_handler.py +++ b/PyDC/PyDC/bitstream_handler.py @@ -152,10 +152,18 @@ def get_block_info(self, codepoint_stream): # Get the complete block content codepoints = tuple(itertools.islice(codepoint_stream, block_length)) - log.debug("content of '%s':" % self.cfg.BLOCK_TYPE_DICT[block_type]) - log.debug("-"*79) - log.debug(pformat_codepoints(codepoints)) - log.debug("-"*79) + try: + verbose_block_type = self.cfg.BLOCK_TYPE_DICT[block_type] + except KeyError: + log.error("Blocktype unknown!") + print pformat_codepoints(codepoints) + sys.exit() + verbose_block_type = hex(block_type) + +# log.debug("content of '%s':" % verbose_block_type) +# log.debug("-"*79) +# log.debug(pformat_codepoints(codepoints)) +# log.debug("-"*79) real_block_len = len(codepoints) if real_block_len == block_length: From 8a208aed0eaa896e19379fb4a8eac1d7be0bd7ad Mon Sep 17 00:00:00 2001 From: Jens Diemer Date: Sat, 7 Sep 2013 10:27:46 +0200 Subject: [PATCH 077/151] set "machine code start/load address" from filename See: http://archive.worldofdragon.org/phpBB3/viewtopic.php?f=8&t=4341&p=9094#p9094 --- PyDC/PyDC/CassetteObjects.py | 11 +++++++---- PyDC/PyDC/tests.py | 6 +++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/PyDC/PyDC/CassetteObjects.py b/PyDC/PyDC/CassetteObjects.py index 75e9ff57..08b8d3e0 100644 --- a/PyDC/PyDC/CassetteObjects.py +++ b/PyDC/PyDC/CassetteObjects.py @@ -312,8 +312,6 @@ def create_from_wave(self, codepoints): self.filename = codepoints2string(raw_filename).rstrip() print "\nFilename: %s" % repr(self.filename) - - self.file_type = codepoints[8] if not self.file_type in self.cfg.FILETYPE_DICT: @@ -369,8 +367,13 @@ def get_filename_block_as_codepoints(self): codepoints.append(self.cfg.BASIC_ASCII) # one byte ASCII flag codepoints.append(0x00) # one byte gap flag (00=no gaps, FF=gaps) # FIXME, see: http://five.pairlist.net/pipermail/coco/2013-August/070938.html - codepoints += [0x0c, 0x00] # two bytes machine code starting address - codepoints += [0x0c, 0x00] # two bytes machine code loading address + + # two bytes machine code starting address: + codepoints += [ord(self.filename[0]), ord(self.filename[1])] + + # two bytes machine code loading address: + codepoints += [ord(self.filename[2]), ord(self.filename[3])] + log.debug("filename block: %s" % pformat_codepoints(codepoints)) return codepoints diff --git a/PyDC/PyDC/tests.py b/PyDC/PyDC/tests.py index 8269b00a..9d367fce 100755 --- a/PyDC/PyDC/tests.py +++ b/PyDC/PyDC/tests.py @@ -156,10 +156,10 @@ def test_bas2cas01(self): ("\x00", "File type: BASIC programm (0x00)"), ("\xff", "format: ASCII BASIC (0xff)"), ("\x00", "gap flag (00=no gaps, FF=gaps)"), - ("\x0c\x00", "machine code starting address"), - ("\x0c\x00", "machine code loading address"), + ("HE", "machine code starting address"), + ("LL", "machine code loading address"), - ("\x92", "block checksum"), + ("\x9f", "block checksum"), ("U", "magic byte block terminator 0x3C"), ("UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU", "35x Leadin bytes 0x55"), From 3de59b0c45e0f6560e29cb98f24661834eb16684 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Sat, 7 Sep 2013 23:07:37 +0200 Subject: [PATCH 078/151] handle wav/cas with code in more than one block --- PyDC/PyDC/CassetteObjects.py | 53 ++++++++++++++++++++++------------ PyDC/PyDC/bitstream_handler.py | 6 ++-- PyDC/PyDC/tests.py | 6 ++++ 3 files changed, 44 insertions(+), 21 deletions(-) diff --git a/PyDC/PyDC/CassetteObjects.py b/PyDC/PyDC/CassetteObjects.py index 08b8d3e0..5ee97926 100644 --- a/PyDC/PyDC/CassetteObjects.py +++ b/PyDC/PyDC/CassetteObjects.py @@ -11,19 +11,17 @@ :license: GNU GPL v3 or above, see LICENSE for more details. """ +import itertools import logging import os import sys # own modules from basic_tokens import bytes2codeline -from configs import Dragon32Config from utils import get_word, codepoints2string, string2codepoint, LOG_LEVEL_DICT, \ LOG_FORMATTER, pformat_codepoints - - log = logging.getLogger("PyDC") @@ -434,6 +432,10 @@ def __init__(self, cfg): self.current_file = None self.wav = None # Bitstream2Wave instance only if write_wave() used! + # temp storage for code block + self.temp_file_data = [] + self.temp_data_length = 0 + def add_from_bas(self, filename): with open(filename, "r") as f: file_content = f.read() @@ -442,16 +444,30 @@ def add_from_bas(self, filename): self.current_file.create_from_bas(filename, file_content) self.files.append(self.current_file) + def add_code(self): + if self.current_file is not None and self.temp_file_data: + self.current_file.add_block_data(self.temp_data_length, self.temp_file_data) + self.temp_file_data = [] + self.temp_data_length = 0 + def add_block(self, block_type, block_length, block_codepoints): if block_type == self.cfg.EOF_BLOCK: + self.add_code() return elif block_type == self.cfg.FILENAME_BLOCK: + self.add_code() self.current_file = CassetteFile(self.cfg) self.current_file.create_from_wave(block_codepoints) log.info("Add file %s" % repr(self.current_file)) self.files.append(self.current_file) elif block_type == self.cfg.DATA_BLOCK: - self.current_file.add_block_data(block_length, block_codepoints) + # store code until end marker + block = list(itertools.islice(block_codepoints, block_length)) + self.temp_file_data += block + self.temp_data_length += block_length + if block[-2:] == self.cfg.BASIC_CODE_END: # code terminator is [0x00, 0x00] + # code block ends + self.add_code() else: raise TypeError("Block type %s unkown!" & hex(block_type)) @@ -605,27 +621,28 @@ def pprint_codepoint_stream(self): import subprocess # bas -> wav - subprocess.Popen([sys.executable, "../PyDC_cli.py", - "--verbosity=10", -# "--verbosity=5", -# "--logfile=5", -# "--log_format=%(module)s %(lineno)d: %(message)s", - "../test_files/HelloWorld1.bas", "--dst=../test.wav" -# "../test_files/HelloWorld1.bas", "--dst=../test.cas" - ]).wait() +# subprocess.Popen([sys.executable, "../PyDC_cli.py", +# "--verbosity=10", +# # "--verbosity=5", +# # "--logfile=5", +# # "--log_format=%(module)s %(lineno)d: %(message)s", +# "../test_files/HelloWorld1.bas", "--dst=../test.wav" +# # "../test_files/HelloWorld1.bas", "--dst=../test.cas" +# ]).wait() # print "\n"*3 # print "="*79 # print "\n"*3 # # # # wav -> bas -# subprocess.Popen([sys.executable, "../PyDC_cli.py", -# # "--verbosity=10", -# "--verbosity=7", -# # "../test.wav", "--dst=../test.bas", + subprocess.Popen([sys.executable, "../PyDC_cli.py", +# "--verbosity=10", + "--verbosity=7", +# "../test.wav", "--dst=../test.bas", # "../test.cas", "--dst=../test.bas", -# # "../test_files/HelloWorld1 origin.wav", "--dst=../test_files/HelloWorld1.bas", -# ]).wait() +# "../test_files/HelloWorld1 origin.wav", "--dst=../test_files/HelloWorld1.bas", + "../test_files/LineNumber Test 02.wav", "--dst=../test.bas", + ]).wait() # # print "-- END --" diff --git a/PyDC/PyDC/bitstream_handler.py b/PyDC/PyDC/bitstream_handler.py index 5bcdd50f..c471859a 100755 --- a/PyDC/PyDC/bitstream_handler.py +++ b/PyDC/PyDC/bitstream_handler.py @@ -14,16 +14,15 @@ """ +import functools import itertools import logging import os import sys # own modules -from configs import Dragon32Config from CassetteObjects import Cassette from PyDC.utils import count_the_same -import functools log = logging.getLogger("PyDC") @@ -128,6 +127,8 @@ def feed(self, bitstream): print "*** block type: 0x%x (%s)" % (block_type, block_type_name) + self.cassette.add_block(block_type, block_length, codepoints) + if block_type == self.cfg.EOF_BLOCK: log.info("EOF-Block found") break @@ -140,7 +141,6 @@ def feed(self, bitstream): print "-"*79 break - self.cassette.add_block(block_type, block_length, codepoints) print "="*79 def get_block_info(self, codepoint_stream): diff --git a/PyDC/PyDC/tests.py b/PyDC/PyDC/tests.py index 9d367fce..0f8ae24f 100755 --- a/PyDC/PyDC/tests.py +++ b/PyDC/PyDC/tests.py @@ -244,6 +244,12 @@ def test_bas2ascii_wav(self): '30 NEXT I\n' )) + def test_more_than_one_code_block(self): + """ + TODO: Test if wav/cas has more than 256Bytes code (code in more than one block) + """ + pass + if __name__ == '__main__': # log = logging.getLogger("PyDC") From 981f7451079e661e679641a4c15f6d0cc0daac47 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Mon, 9 Sep 2013 09:27:38 +0200 Subject: [PATCH 079/151] Bugfixes: * Use GAP-Flag for ASCII BASIC * Don't yield magic byte at block end * Don't yield machine code starting/loading address in BASIC files --- PyDC/PyDC/CassetteObjects.py | 73 +++++++++++++++++++++++----------- PyDC/PyDC/bitstream_handler.py | 10 ++--- PyDC/PyDC/configs.py | 6 ++- PyDC/PyDC/tests.py | 11 ++--- 4 files changed, 62 insertions(+), 38 deletions(-) diff --git a/PyDC/PyDC/CassetteObjects.py b/PyDC/PyDC/CassetteObjects.py index 5ee97926..91ded698 100644 --- a/PyDC/PyDC/CassetteObjects.py +++ b/PyDC/PyDC/CassetteObjects.py @@ -324,27 +324,33 @@ def create_from_wave(self, codepoints): elif self.file_type == self.cfg.FTYPE_BIN: raise NotImplementedError("Binary files are not supported, yet.") - ascii_flag = codepoints[9] - log.info("Raw ASCII flag is: %s" % repr(ascii_flag)) - if ascii_flag == self.cfg.BASIC_TOKENIZED: + self.ascii_flag = codepoints[9] + log.info("Raw ASCII flag is: %s" % repr(self.ascii_flag)) + if self.ascii_flag == self.cfg.BASIC_TOKENIZED: self.is_tokenized = True - elif ascii_flag == self.cfg.BASIC_ASCII: + elif self.ascii_flag == self.cfg.BASIC_ASCII: self.is_tokenized = False else: - raise NotImplementedError("Unknown BASIC type: '%s'" % hex(ascii_flag)) + raise NotImplementedError("Unknown BASIC type: '%s'" % hex(self.ascii_flag)) - log.info("ASCII flag: %s" % self.cfg.BASIC_TYPE_DICT[ascii_flag]) + log.info("ASCII flag: %s" % self.cfg.BASIC_TYPE_DICT[self.ascii_flag]) self.gap_flag = codepoints[10] log.info("gap flag is %s (0x00=no gaps, 0xff=gaps)" % hex(self.gap_flag)) - codepoints = iter(codepoints) + # machine code starting/loading address + if self.file_type != self.cfg.FTYPE_BASIC: # BASIC programm (0x00) + codepoints = iter(codepoints) - self.start_address = get_word(codepoints) - log.info("machine code starting address: %s" % hex(self.start_address)) + self.start_address = get_word(codepoints) + log.info("machine code starting address: %s" % hex(self.start_address)) - self.load_address = get_word(codepoints) - log.info("machine code loading address: %s" % hex(self.load_address)) + self.load_address = get_word(codepoints) + log.info("machine code loading address: %s" % hex(self.load_address)) + else: + # not needed in BASIC files + # http://archive.worldofdragon.org/phpBB3/viewtopic.php?f=8&t=4341&p=9109#p9109 + pass self.file_content = FileContent(self.cfg) @@ -359,18 +365,39 @@ def add_block_data(self, block_length, codepoints): print "*"*79 def get_filename_block_as_codepoints(self): + """ + TODO: Support tokenized BASIC. Now we only create ASCII BASIC. + """ codepoints = [] codepoints += list(string2codepoint(self.filename.ljust(8, " "))) codepoints.append(self.cfg.FTYPE_BASIC) # one byte file type codepoints.append(self.cfg.BASIC_ASCII) # one byte ASCII flag - codepoints.append(0x00) # one byte gap flag (00=no gaps, FF=gaps) - # FIXME, see: http://five.pairlist.net/pipermail/coco/2013-August/070938.html - - # two bytes machine code starting address: - codepoints += [ord(self.filename[0]), ord(self.filename[1])] - - # two bytes machine code loading address: - codepoints += [ord(self.filename[2]), ord(self.filename[3])] + + + # one byte gap flag (0x00=no gaps, 0xFF=gaps) + # http://archive.worldofdragon.org/phpBB3/viewtopic.php?f=8&t=4231&p=9110#p9110 + codepoints.append(self.cfg.GAPS) + +# if self.ascii_flag == self.cfg.BASIC_ASCII: +# codepoints.append(self.cfg.GAPS) +# elif self.ascii_flag == self.cfg.BASIC_TOKENIZED: +# codepoints.append(self.cfg.NO_GAPS) +# else: +# raise NotImplementedError("Unknown BASIC type: '%s'" % hex(self.ascii_flag)) + + # machine code starting/loading address + if self.file_type != self.cfg.FTYPE_BASIC: # BASIC programm (0x00) + codepoints = iter(codepoints) + + self.start_address = get_word(codepoints) + log.info("machine code starting address: %s" % hex(self.start_address)) + + self.load_address = get_word(codepoints) + log.info("machine code loading address: %s" % hex(self.load_address)) + else: + # not needed in BASIC files + # http://archive.worldofdragon.org/phpBB3/viewtopic.php?f=8&t=4341&p=9109#p9109 + pass log.debug("filename block: %s" % pformat_codepoints(codepoints)) return codepoints @@ -503,8 +530,8 @@ def block2codepoint_stream(self, block_type, block_codepoints): checksum = checksum & 0xFF log.debug("yield calculated checksum %s" % hex(checksum)) yield checksum - log.debug("yield magic byte %s" % hex(self.cfg.MAGIC_BYTE)) - yield self.cfg.MAGIC_BYTE # 0x55 +# log.debug("yield magic byte %s" % hex(self.cfg.MAGIC_BYTE)) +# yield self.cfg.MAGIC_BYTE # 0x55 else: log.debug("content of '%s':" % self.cfg.BLOCK_TYPE_DICT[block_type]) log.debug("-"*79) @@ -520,8 +547,8 @@ def block2codepoint_stream(self, block_type, block_codepoints): checksum = checksum & 0xFF log.debug("yield calculated checksum %s" % hex(checksum)) yield checksum - log.debug("yield magic byte %s" % hex(self.cfg.MAGIC_BYTE)) - yield self.cfg.MAGIC_BYTE # 0x55 +# log.debug("yield magic byte %s" % hex(self.cfg.MAGIC_BYTE)) +# yield self.cfg.MAGIC_BYTE # 0x55 def codepoint_stream(self): if self.wav: diff --git a/PyDC/PyDC/bitstream_handler.py b/PyDC/PyDC/bitstream_handler.py index c471859a..78893d59 100755 --- a/PyDC/PyDC/bitstream_handler.py +++ b/PyDC/PyDC/bitstream_handler.py @@ -189,11 +189,11 @@ def get_block_info(self, codepoint_stream): # Check if the magic byte exists - magic_byte = next(codepoint_stream) - if magic_byte != self.cfg.MAGIC_BYTE: - log.error("Magic Byte %s is not %s" % (hex(magic_byte), hex(self.cfg.MAGIC_BYTE))) - else: - log.info("Magic Byte %s, ok." % hex(magic_byte)) +# magic_byte = next(codepoint_stream) +# if magic_byte != self.cfg.MAGIC_BYTE: +# log.error("Magic Byte %s is not %s" % (hex(magic_byte), hex(self.cfg.MAGIC_BYTE))) +# else: +# log.info("Magic Byte %s, ok." % hex(magic_byte)) return block_type, block_length, codepoints diff --git a/PyDC/PyDC/configs.py b/PyDC/PyDC/configs.py index 44d6fa12..c72da28d 100644 --- a/PyDC/PyDC/configs.py +++ b/PyDC/PyDC/configs.py @@ -89,8 +89,6 @@ class Dragon32Config(BaseConfig): SYNC_BYTE_CODEPOINT = 0x3C # 00111100 MAX_SYNC_BYTE_SEARCH = 600 # search size in **Bytes** - MAGIC_BYTE = 0x55 # 10101010 - # Block types: FILENAME_BLOCK = 0x00 DATA_BLOCK = 0x01 @@ -119,6 +117,10 @@ class Dragon32Config(BaseConfig): BASIC_ASCII:"ASCII BASIC (0xff)", } + # The gap flag + NO_GAPS = 0x00 + GAPS = 0xff + BASIC_CODE_END = [0x00, 0x00] # Mark the end of the code diff --git a/PyDC/PyDC/tests.py b/PyDC/PyDC/tests.py index 0f8ae24f..9cf08a4b 100755 --- a/PyDC/PyDC/tests.py +++ b/PyDC/PyDC/tests.py @@ -150,17 +150,14 @@ def test_bas2cas01(self): ("<", "Sync byte 0x3C"), ("\x00", "block type: filename block (0x00)"), - ("\x0f", "block length (15Bytes)"), + ("\x0b", "block length (11Bytes)"), ("HELLOWOR", "filename"), ("\x00", "File type: BASIC programm (0x00)"), ("\xff", "format: ASCII BASIC (0xff)"), - ("\x00", "gap flag (00=no gaps, FF=gaps)"), - ("HE", "machine code starting address"), - ("LL", "machine code loading address"), + ("\xff", "gap flag (00=no gaps, FF=gaps)"), - ("\x9f", "block checksum"), - ("U", "magic byte block terminator 0x3C"), + ("u", "block checksum"), ("UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU", "35x Leadin bytes 0x55"), ("<", "Sync byte 0x3C"), @@ -173,7 +170,6 @@ def test_bas2cas01(self): ), ("\x00\x00", "code end terminator"), ("\x91", "block checksum"), - ("U", "magic byte block terminator 0x3C"), ("UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU", "35x Leadin bytes 0x55"), ("<", "Sync byte 0x3C"), @@ -181,7 +177,6 @@ def test_bas2cas01(self): ("\xff", "block type: end-of-file block (0xff)"), ("\x00", "block length (0Bytes)"), ("\xff", "block checksum"), - ("U", "magic byte block terminator 0x3C"), ) dest_content = iter(dest_content) From df6d96ea353c4031db24fd4e8e08ce2e719f6685 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Mon, 9 Sep 2013 10:41:17 +0200 Subject: [PATCH 080/151] remove BASIC code end terminator, seems it's not needed. --- PyDC/PyDC/CassetteObjects.py | 13 ++++++++++--- PyDC/PyDC/configs.py | 4 ---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/PyDC/PyDC/CassetteObjects.py b/PyDC/PyDC/CassetteObjects.py index 91ded698..0c90bbd8 100644 --- a/PyDC/PyDC/CassetteObjects.py +++ b/PyDC/PyDC/CassetteObjects.py @@ -262,8 +262,6 @@ def get_as_codepoints(self): result += list(code_line.get_as_codepoints()) result.append(delim) - result += self.cfg.BASIC_CODE_END - log.debug("code: %s" % repr(result)) return result def get_ascii_codeline(self): @@ -403,7 +401,16 @@ def get_filename_block_as_codepoints(self): return codepoints def get_code_block_as_codepoints(self): - return self.file_content.get_as_codepoints() + result = self.file_content.get_as_codepoints() + + # XXX: Is a code block end terminator needed? + # e.g.: +# if self.is_tokenized: +# result += [0x00, 0x00] +# else: +# result.append(0x0d) # 0x0d == \r + + return result def print_debug_info(self): print "\tFilename: '%s'" % self.filename diff --git a/PyDC/PyDC/configs.py b/PyDC/PyDC/configs.py index c72da28d..36578be5 100644 --- a/PyDC/PyDC/configs.py +++ b/PyDC/PyDC/configs.py @@ -121,10 +121,6 @@ class Dragon32Config(BaseConfig): NO_GAPS = 0x00 GAPS = 0xff - BASIC_CODE_END = [0x00, 0x00] # Mark the end of the code - - - if __name__ == "__main__": import doctest From b54e4a633ca2741d8cae60fd9414eb49f1d608d9 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Mon, 9 Sep 2013 11:30:46 +0200 Subject: [PATCH 081/151] * Use one convert() method for all ways. * Move some convert code into Cassette() class * code cleanup * update README --- PyDC/PyDC/CassetteObjects.py | 79 ++++++++++++++++++-------------- PyDC/PyDC/__init__.py | 83 ++++++++++++++-------------------- PyDC/PyDC/bitstream_handler.py | 21 ++++----- PyDC/PyDC/tests.py | 28 ++++++------ PyDC/PyDC_cli.py | 31 +++---------- PyDC/README.creole | 23 ++++++---- 6 files changed, 122 insertions(+), 143 deletions(-) diff --git a/PyDC/PyDC/CassetteObjects.py b/PyDC/PyDC/CassetteObjects.py index 0c90bbd8..ce5d99e2 100644 --- a/PyDC/PyDC/CassetteObjects.py +++ b/PyDC/PyDC/CassetteObjects.py @@ -20,6 +20,8 @@ from basic_tokens import bytes2codeline from utils import get_word, codepoints2string, string2codepoint, LOG_LEVEL_DICT, \ LOG_FORMATTER, pformat_codepoints +from wave2bitstream import Wave2Bitstream, Bitstream2Wave +from bitstream_handler import BitstreamHandler, CasStream, BytestreamHandler log = logging.getLogger("PyDC") @@ -215,8 +217,6 @@ def add_ascii_block(self, block_length, data): """ data = iter(data) - basic_code_end = "".join([chr(i) for i in self.cfg.BASIC_CODE_END]) - data.next() # Skip first \r byte_count = 1 # incl. first \r while True: @@ -227,11 +227,6 @@ def add_ascii_block(self, block_length, data): log.warning("code ended.") break - if code == basic_code_end: - log.debug("BASIC code end marker %s found." % pformat_codepoints(self.cfg.BASIC_CODE_END)) - byte_count += len(code) - break - byte_count += len(code) + 1 # and \r consumed in iter() try: @@ -252,7 +247,9 @@ def add_ascii_block(self, block_length, data): print "%i Bytes parsed" % byte_count if block_length != byte_count: - print "\nERROR: Block length value %i is not equal to parsed bytes!" % block_length + log.error( + "Block length value %i is not equal to parsed bytes!" % block_length + ) def get_as_codepoints(self): result = [] @@ -370,7 +367,6 @@ def get_filename_block_as_codepoints(self): codepoints += list(string2codepoint(self.filename.ljust(8, " "))) codepoints.append(self.cfg.FTYPE_BASIC) # one byte file type codepoints.append(self.cfg.BASIC_ASCII) # one byte ASCII flag - # one byte gap flag (0x00=no gaps, 0xFF=gaps) # http://archive.worldofdragon.org/phpBB3/viewtopic.php?f=8&t=4231&p=9110#p9110 @@ -382,7 +378,7 @@ def get_filename_block_as_codepoints(self): # codepoints.append(self.cfg.NO_GAPS) # else: # raise NotImplementedError("Unknown BASIC type: '%s'" % hex(self.ascii_flag)) - + # machine code starting/loading address if self.file_type != self.cfg.FTYPE_BASIC: # BASIC programm (0x00) codepoints = iter(codepoints) @@ -467,8 +463,20 @@ def __init__(self, cfg): self.wav = None # Bitstream2Wave instance only if write_wave() used! # temp storage for code block - self.temp_file_data = [] - self.temp_data_length = 0 + self.buffer = [] + self.buffered_block_length = 0 + + def add_from_wav(self, source_file): + bitstream = iter(Wave2Bitstream(source_file, self.cfg)) + + # store bitstream into python objects + bh = BitstreamHandler(self, self.cfg) + bh.feed(bitstream) + + def add_from_cas(self, source_file): + cas_stream = CasStream(source_file) + bh = BytestreamHandler(self, self.cfg) + bh.feed(cas_stream) def add_from_bas(self, filename): with open(filename, "r") as f: @@ -478,30 +486,33 @@ def add_from_bas(self, filename): self.current_file.create_from_bas(filename, file_content) self.files.append(self.current_file) - def add_code(self): - if self.current_file is not None and self.temp_file_data: - self.current_file.add_block_data(self.temp_data_length, self.temp_file_data) - self.temp_file_data = [] - self.temp_data_length = 0 + def buffer2file(self): + """ + add the code buffer content to CassetteFile() instance + """ + if self.current_file is not None and self.buffer: + self.current_file.add_block_data(self.buffered_block_length, self.buffer) + self.buffer = [] + self.buffered_block_length = 0 + + def buffer_block(self, block_type, block_length, block_codepoints): + + block = tuple(itertools.islice(block_codepoints, block_length)) + log.debug("pprint block: %s" % pformat_codepoints(block)) - def add_block(self, block_type, block_length, block_codepoints): if block_type == self.cfg.EOF_BLOCK: - self.add_code() + self.buffer2file() return elif block_type == self.cfg.FILENAME_BLOCK: - self.add_code() + self.buffer2file() self.current_file = CassetteFile(self.cfg) - self.current_file.create_from_wave(block_codepoints) + self.current_file.create_from_wave(block) log.info("Add file %s" % repr(self.current_file)) self.files.append(self.current_file) elif block_type == self.cfg.DATA_BLOCK: # store code until end marker - block = list(itertools.islice(block_codepoints, block_length)) - self.temp_file_data += block - self.temp_data_length += block_length - if block[-2:] == self.cfg.BASIC_CODE_END: # code terminator is [0x00, 0x00] - # code block ends - self.add_code() + self.buffer += block + self.buffered_block_length += block_length else: raise TypeError("Block type %s unkown!" & hex(block_type)) @@ -537,8 +548,6 @@ def block2codepoint_stream(self, block_type, block_codepoints): checksum = checksum & 0xFF log.debug("yield calculated checksum %s" % hex(checksum)) yield checksum -# log.debug("yield magic byte %s" % hex(self.cfg.MAGIC_BYTE)) -# yield self.cfg.MAGIC_BYTE # 0x55 else: log.debug("content of '%s':" % self.cfg.BLOCK_TYPE_DICT[block_type]) log.debug("-"*79) @@ -554,8 +563,6 @@ def block2codepoint_stream(self, block_type, block_codepoints): checksum = checksum & 0xFF log.debug("yield calculated checksum %s" % hex(checksum)) yield checksum -# log.debug("yield magic byte %s" % hex(self.cfg.MAGIC_BYTE)) -# yield self.cfg.MAGIC_BYTE # 0x55 def codepoint_stream(self): if self.wav: @@ -592,8 +599,8 @@ def codepoint_stream(self): if self.wav: self.wav.write_silence(sec=0.1) - def write_wave(self, wav): - self.wav = wav # Bitstream2Wave instance + def write_wave(self, destination_file): + wav = Bitstream2Wave(destination_file, self.cfg) for codepoint in self.codepoint_stream(): if isinstance(codepoint, (tuple, list)): for item in codepoint: @@ -602,6 +609,8 @@ def write_wave(self, wav): assert isinstance(codepoint, int), "Codepoint %s is not int/hex" % repr(codepoint) wav.write_codepoint(codepoint) + wav.close() + def write_cas(self, destination_file): log.info("Create %s..." % repr(destination_file)) with open(destination_file, "wb") as f: @@ -613,8 +622,8 @@ def write_cas(self, destination_file): f.write(chr(codepoint)) print "\nFile %s saved." % repr(destination_file) - def save_bas(self, destination_file): - dest_filename, dest_ext = os.path.splitext(destination_file) + def write_bas(self, destination_file): + dest_filename = os.path.splitext(destination_file)[0] for file_obj in self.files: bas_filename = file_obj.filename # Filename from CSAVE argument diff --git a/PyDC/PyDC/__init__.py b/PyDC/PyDC/__init__.py index 1d3f8e62..2d4ef7db 100644 --- a/PyDC/PyDC/__init__.py +++ b/PyDC/PyDC/__init__.py @@ -9,11 +9,12 @@ :license: GNU GPL v3 or above, see LICENSE for more details. """ +import os +import sys + from CassetteObjects import Cassette -from bitstream_handler import BitstreamHandler, CasStream, BytestreamHandler from utils import print_bitlist from wave2bitstream import Wave2Bitstream, Bitstream2Wave -import sys __version__ = (0, 1, 0, 'dev') @@ -26,63 +27,49 @@ def analyze(wave_file, cfg): wb = Wave2Bitstream(wave_file, cfg) wb.analyze() -def bas2cas(source_filepath, destination_filepath, cfg): - """ - Create a .cas file from a existing .bas file - """ - c = Cassette(cfg) - c.add_from_bas(source_filepath) - c.print_debug_info() - c.write_cas(destination_filepath) - -def cas2bas(source_filepath, destination_filepath, cfg): +def convert(source_file, destination_file, cfg): """ - Read .cas file and create a .bas file + convert in every way. """ - cas_stream = CasStream(source_filepath) - bh = BytestreamHandler(cfg) - bh.feed(cas_stream) + source_ext = os.path.splitext(source_file)[1] + source_ext = source_ext.lower() - # save .bas file - bh.cassette.save_bas(destination_filepath) + dest_ext = os.path.splitext(destination_file)[1] + dest_ext = dest_ext.lower() -# c = Cassette(cfg) -# c.add_from_cas(source_filepath) -# c.print_debug_info() -# c.save_bas(destination_filepath) + if source_ext not in (".wav", ".cas", ".bas"): + raise AssertionError( + "Source file type %r not supported." % repr(source_ext) + ) + if dest_ext not in (".wav", ".cas", ".bas"): + raise AssertionError( + "Destination file type %r not supported." % repr(dest_ext) + ) + print "Convert %s -> %s" % (source_ext, dest_ext) -def bas2wav(source_filepath, destination_filepath, cfg): - """ - Create a wave file from a existing .bas file - """ c = Cassette(cfg) - c.add_from_bas(source_filepath) - c.print_debug_info() + if source_ext == ".wav": + c.add_from_wav(source_file) + elif source_ext == ".cas": + c.add_from_cas(source_file) + elif source_ext == ".bas": + c.add_from_bas(source_file) + else: + raise RuntimeError # Should never happen - wav = Bitstream2Wave(destination_filepath, cfg) - - c.write_wave(wav) - - wav.close() - - - - -def wav2bas(source_filepath, destination_filepath, cfg): - """ - get bitstream generator from WAVE file - """ - bitstream = iter(Wave2Bitstream(source_filepath, cfg)) - - # store bitstream into python objects - bh = BitstreamHandler(cfg) - bh.feed(bitstream) + c.print_debug_info() - # save .bas file - bh.cassette.save_bas(destination_filepath) + if dest_ext == ".wav": + c.write_wave(destination_file) + elif dest_ext == ".cas": + c.write_cas(destination_file) + elif dest_ext == ".bas": + c.write_bas(destination_file) + else: + raise RuntimeError # Should never happen diff --git a/PyDC/PyDC/bitstream_handler.py b/PyDC/PyDC/bitstream_handler.py index 78893d59..91caedd4 100755 --- a/PyDC/PyDC/bitstream_handler.py +++ b/PyDC/PyDC/bitstream_handler.py @@ -20,19 +20,12 @@ import os import sys -# own modules -from CassetteObjects import Cassette -from PyDC.utils import count_the_same - - log = logging.getLogger("PyDC") - # own modules from utils import find_iter_window, iter_steps, MaxPosArraived, \ print_bitlist, bits2codepoint, list2str, bitstream2codepoints, \ - PatternNotFound, LOG_FORMATTER, codepoints2bitstream, pformat_codepoints -from wave2bitstream import Wave2Bitstream + PatternNotFound, count_the_same, codepoints2bitstream, pformat_codepoints DISPLAY_BLOCK_COUNT = 8 # How many bit block should be printet in one line? @@ -94,9 +87,9 @@ def print_as_hex(block_codepoints): class BitstreamHandlerBase(object): - def __init__(self, cfg): + def __init__(self, cassette, cfg): + self.cassette = cassette self.cfg = cfg - self.cassette = Cassette(cfg) def feed(self, bitstream): while True: @@ -125,9 +118,11 @@ def feed(self, bitstream): break - print "*** block type: 0x%x (%s)" % (block_type, block_type_name) + log.debug( + "block type: 0x%x (%s)" % (block_type, block_type_name) + ) - self.cassette.add_block(block_type, block_length, codepoints) + self.cassette.buffer_block(block_type, block_length, codepoints) if block_type == self.cfg.EOF_BLOCK: log.info("EOF-Block found") @@ -143,6 +138,8 @@ def feed(self, bitstream): print "="*79 + self.cassette.buffer2file() + def get_block_info(self, codepoint_stream): block_type = next(codepoint_stream) log.info("raw block type: %s (%s)" % (hex(block_type), repr(block_type))) diff --git a/PyDC/PyDC/tests.py b/PyDC/PyDC/tests.py index 9cf08a4b..c5edb60b 100755 --- a/PyDC/PyDC/tests.py +++ b/PyDC/PyDC/tests.py @@ -16,7 +16,7 @@ import itertools # own modules -from __init__ import wav2bas, bas2cas, cas2bas, bas2wav +from __init__ import convert import configs from wave2bitstream import Wave2Bitstream @@ -56,7 +56,7 @@ def _get_and_delete_dst(self, destination_filepath, delete=True): def test_wav2bas01(self): source_filepath = self._src_file_path("HelloWorld1 xroar.wav") destination_filepath = self._dst_file_path("unittest_wav2bas01.bas") - wav2bas(source_filepath, destination_filepath, self.cfg) + convert(source_filepath, destination_filepath, self.cfg) # no filename used in CSAVE: destination_filepath = self._dst_file_path("unittest_wav2bas01_.bas") @@ -72,7 +72,7 @@ def test_wav2bas01(self): def test_wav2bas02(self): source_filepath = self._src_file_path("HelloWorld1 origin.wav") destination_filepath = self._dst_file_path("unittest_wav2bas02.bas") - wav2bas(source_filepath, destination_filepath, self.cfg) + convert(source_filepath, destination_filepath, self.cfg) # no filename used in CSAVE: destination_filepath = self._dst_file_path("unittest_wav2bas02_.bas") @@ -88,7 +88,7 @@ def test_wav2bas02(self): def test_wav2bas03(self): source_filepath = self._src_file_path("LineNumber Test 01.wav") destination_filepath = self._dst_file_path("unittest_wav2bas03.bas") - wav2bas(source_filepath, destination_filepath, self.cfg) + convert(source_filepath, destination_filepath, self.cfg) # filename 'LINENO01' used in CSAVE: destination_filepath = self._dst_file_path("unittest_wav2bas03_LINENO01.bas") @@ -108,7 +108,7 @@ def test_wav2bas03(self): def test_wav2bas04(self): source_filepath = self._src_file_path("LineNumber Test 02.wav") destination_filepath = self._dst_file_path("unittest_wav2bas03.bas") - wav2bas(source_filepath, destination_filepath, self.cfg) + convert(source_filepath, destination_filepath, self.cfg) # filename 'LINENO02' used in CSAVE: destination_filepath = self._dst_file_path("unittest_wav2bas03_LINENO02.bas") @@ -140,7 +140,7 @@ def test_bas2cas01(self): cfg = configs.Dragon32Config() cfg.LEAD_BYTE_LEN = 35 - bas2cas(source_filepath, destination_filepath, cfg) + convert(source_filepath, destination_filepath, cfg) dest_content = self._get_and_delete_dst(destination_filepath) # print repr(dest_content) @@ -163,13 +163,12 @@ def test_bas2cas01(self): ("<", "Sync byte 0x3C"), ("\x01", "block type: data block (0x01)"), - (":", "block length 0x3a (58Bytes)"), + ("8", "block length 0x38 (56Bytes)"), ( '\r10 FOR I = 1 TO 10\r20 PRINT I;"HELLO WORLD!"\r30 NEXT I\r', "Basic code in ASCII format" ), - ("\x00\x00", "code end terminator"), - ("\x91", "block checksum"), + ("\x8f", "block checksum"), ("UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU", "35x Leadin bytes 0x55"), ("<", "Sync byte 0x3C"), @@ -192,11 +191,11 @@ def test_cas01(self): # create cas source_filepath = self._src_file_path("LineNumberTest.bas") cas_filepath = self._dst_file_path("unittest_LineNumberTest.cas") - bas2cas(source_filepath, cas_filepath, self.cfg) + convert(source_filepath, cas_filepath, self.cfg) # create bas from created cas file destination_filepath = self._dst_file_path("unittest_LineNumberTest.bas") - cas2bas(cas_filepath, destination_filepath, self.cfg) + convert(cas_filepath, destination_filepath, self.cfg) os.remove(cas_filepath) @@ -221,12 +220,12 @@ def test_bas2ascii_wav(self): cfg = configs.Dragon32Config() cfg.LEAD_BYTE_LEN = 128 - bas2wav(source_filepath, destination_filepath, cfg) + convert(source_filepath, destination_filepath, cfg) # read wave and compare source_filepath = self._dst_file_path("unittest_HelloWorld1.wav") destination_filepath = self._dst_file_path("unittest_bas2ascii_wav.bas") - wav2bas(source_filepath, destination_filepath, self.cfg) + convert(source_filepath, destination_filepath, self.cfg) # filename 'HELLOWOR' used in CSAVE: destination_filepath = self._dst_file_path("unittest_bas2ascii_wav_HELLOWOR.bas") @@ -252,7 +251,8 @@ def test_more_than_one_code_block(self): # # logging.ERROR # # logging.INFO # # logging.WARNING -# logging.DEBUG +# # logging.DEBUG +# 7 # ) # log.addHandler(logging.StreamHandler()) diff --git a/PyDC/PyDC_cli.py b/PyDC/PyDC_cli.py index 3a1cf1d7..ad7a2eea 100755 --- a/PyDC/PyDC_cli.py +++ b/PyDC/PyDC_cli.py @@ -11,10 +11,8 @@ import logging import os -import sys -from PyDC import TITLE_LINE, VERSION_STRING, wav2bas, bas2wav, analyze, bas2cas, \ - cas2bas +from PyDC import TITLE_LINE, VERSION_STRING, analyze, convert from PyDC.base_cli import Base_CLI from PyDC.configs import Dragon32Config @@ -33,9 +31,9 @@ def __init__(self): super(PyDC_CLI, self).__init__() self.cfg = Dragon32Config() - self.parser.add_argument("src", help="Source filename (.wav/.bas)") + self.parser.add_argument("src", help="Source filename (.wav/.cas/.bas)") self.parser.add_argument("--dst", - help="Destination filename (.wav/.bas/.cas)" + help="Destination filename (.wav/.cas/.bas)" ) self.parser.add_argument( @@ -110,13 +108,9 @@ def parse_args(self): def run(self): self.args = self.parse_args() - source_filename, source_ext = os.path.splitext(self.source_file) - source_ext = source_ext.lower() - + source_filename = os.path.splitext(self.source_file)[0] if self.args.dst: - dest_filename, dest_ext = os.path.splitext(self.destination_file) - dest_ext = dest_ext.lower() - + dest_filename = os.path.splitext(self.destination_file)[0] self.logfilename = dest_filename + ".log" else: self.logfilename = source_filename + ".log" @@ -135,21 +129,8 @@ def run(self): if self.args.analyze: analyze(self.source_file, self.cfg) - - elif source_ext.startswith(".wav") and dest_ext.startswith(".bas"): - wav2bas(self.source_file, self.destination_file, self.cfg) - elif source_ext.startswith(".bas") and dest_ext.startswith(".wav"): - bas2wav(self.source_file, self.destination_file, self.cfg) - - elif source_ext.startswith(".bas") and dest_ext.startswith(".cas"): - bas2cas(self.source_file, self.destination_file, self.cfg) - elif source_ext.startswith(".cas") and dest_ext.startswith(".bas"): - cas2bas(self.source_file, self.destination_file, self.cfg) - else: - print "ERROR:" - print "%s to %s ???" % (repr(self.source_file), repr(self.destination_file)) - sys.exit(-1) + convert(self.source_file, self.destination_file, self.cfg) if __name__ == "__main__": diff --git a/PyDC/README.creole b/PyDC/README.creole index 94af770f..8a135bb2 100644 --- a/PyDC/README.creole +++ b/PyDC/README.creole @@ -3,7 +3,6 @@ Convert dragon 32 Cassetts WAV files into plain text. - copyleft: 2013 by Jens Diemer license: GNU GPL v3 or above, see LICENSE for more details. @@ -16,6 +15,7 @@ Python dragon 32 converter 0.1.0.dev usage: PyDC_cli.py [-h] [-v] [--verbosity {5,7,0,10,20,30,40,50}] [--logfile {5,7,0,10,20,30,40,50}] [--log_format LOG_FORMAT] [--dst DST] [--analyze] + [--bit_one_hz BIT_ONE_HZ] [--bit_nul_hz BIT_NUL_HZ] [--hz_variation HZ_VARIATION] [--min_volume_ratio MIN_VOLUME_RATIO] [--avg_count AVG_COUNT] [--end_count END_COUNT] @@ -25,7 +25,7 @@ usage: PyDC_cli.py [-h] [-v] [--verbosity {5,7,0,10,20,30,40,50}] Python dragon 32 converter positional arguments: - src Source filename (.wav/.bas) + src Source filename (.wav/.cas/.bas) optional arguments: -h, --help show this help message and exit @@ -39,9 +39,13 @@ optional arguments: --log_format LOG_FORMAT see: http://docs.python.org/2/library/logging.html #logrecord-attributes - --dst DST Destination filename (.wav/.bas) + --dst DST Destination filename (.wav/.cas/.bas) --analyze Display zeror crossing information in the given wave file. + --bit_one_hz BIT_ONE_HZ + Frequency of bit '1' in Hz (default: 2100) + --bit_nul_hz BIT_NUL_HZ + Frequency of bit '0' in Hz (default: 1100) --hz_variation HZ_VARIATION How much Hz can signal scatter to match 1 or 0 bit ? (default: 450) @@ -56,13 +60,18 @@ optional arguments: Sample count that can be around null (default: 1) PyDC v0.1.0.dev copyleft 2013 by htfx.de - Jens Diemer, GNU GPL v3 or above + }}} Example: {{{ ~$ python PyDC_cli.py FooBar.wav --dst=FooBar.bas +~$ python PyDC_cli.py FooBar.wav --dst=FooBar.cas --verbosity=7 }}} +Not every convert variant between {{{.wav}}}, {{{.cas}}}, {{{.bas}}} is useable. +So you can also use {{{.wav}}} as source and {{{.wav}}} as destination, too. + ==== analyze @@ -99,12 +108,8 @@ Some statistics from WAVES files are here: === TODO -support CAS files, too: -* CAS2BAS -* CAS2WAV -* BAS2CAS -* WAV2CAS - +* add tokenized BASIC output, too. Currently {{{.wav}}} and {{{.cas}}} would be always output as ASCII BASIC. +* support for longer listing as 255 characters. Currently the blocks would not be limited to 255Bytes ;( === Links From 621cdecaaf1822695e8298306fce337f303cac1d Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Mon, 9 Sep 2013 15:50:45 +0200 Subject: [PATCH 082/151] remove official example --- ...Examples from the Manual - 39~58 [run].bas | 9 --------- ...Examples from the Manual - 39~58 [run].wav | Bin 5151847 -> 0 bytes 2 files changed, 9 deletions(-) delete mode 100755 PyDC/test_files/Dragon Data Ltd - Examples from the Manual - 39~58 [run].bas delete mode 100755 PyDC/test_files/Dragon Data Ltd - Examples from the Manual - 39~58 [run].wav diff --git a/PyDC/test_files/Dragon Data Ltd - Examples from the Manual - 39~58 [run].bas b/PyDC/test_files/Dragon Data Ltd - Examples from the Manual - 39~58 [run].bas deleted file mode 100755 index 5e82b9b0..00000000 --- a/PyDC/test_files/Dragon Data Ltd - Examples from the Manual - 39~58 [run].bas +++ /dev/null @@ -1,9 +0,0 @@ -10 PMODE 4,1:SCREEN 1,1:PCLS 5:COLOR 0,5 -20 FOR I = 1 TO 1000 -30 X=X+L*SIN(R):Y=Y+L*COS(R) -40 IF X<-128 OR X>128 THEN 90 -50 IF Y<-96 OR Y>95 THEN 90 -60 LINE -(X+128,Y+96),PSET -70 R1=R1+60:R=R1/57.29578:L=L+0.5 -80 NEXT I -90 GOTO 90 \ No newline at end of file diff --git a/PyDC/test_files/Dragon Data Ltd - Examples from the Manual - 39~58 [run].wav b/PyDC/test_files/Dragon Data Ltd - Examples from the Manual - 39~58 [run].wav deleted file mode 100755 index fa3fe9d33bcad28ed023e418fd8ef0ddc5952882..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5151847 zcmeF)zssH1*CzJwAR_|85DY2>0hg{+ihv=I850mG0!k_~3=9Dk0Tolb(H~%Noyvu) z=?v}-?hUSu{{XvFC)DJfc^T%{<=~6uz4y7#^TD{;EY@CY-ACuSpZocoeg2RC?O*+? zfBpad@BjF}{+IvrU;f|!&%gVh{>Oj(;~)RxpZ@z_|NTGyC?-~RaPyASXG_~nOh-n@MN^zox7&tANG_4ey;zyI;)Uw?o1{=bx{^siM&i?k!A0QMM z2}*+#AyrrzT89@RmY6AOiyI@?*g1Od;|D&1;bSO1isR!*K9c2QX+E0g0_!s zs_Wy*KCpr^g_yQ*|IDx?l3{GHh0)rD6oWS4&1}88$fx!t3PGE2XgA*8>z~BT1 zConjH!3hjbU~mG16BwMp-~z~BT1ConjH!3hjbU~mG16BwMp-~z~BT1ConjH z!3hjbU~mG16BwMp-~z~BT1ConjH!3hjbU~mG16BwMp-~z~BT1ConjH!3hjb zU~mG16BwMp-~z~BT1ConjH!3hjbU~mG16BwMp-~z~BT1ConjH!3hjbU~mG1 z6BwMp-~z~BT1ConjH!3hjbU~mG16BwMp-~z~BT1ConjH!3hjbU~mG16BwMp z-~z~BT1ConjH!3hjbU~mG16BwMp-~z~BT1ConjH!3hjbU~mG16BwMp-~

  • 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

    *{8BkWuMAE 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 C2HNBR From a293f944e00eef21402109b80fbc55bd0a4e7860 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Mon, 9 Sep 2013 15:59:49 +0200 Subject: [PATCH 083/151] add '--case_convert' cli parameter. See README --- PyDC/PyDC/CassetteObjects.py | 6 +++ PyDC/PyDC/configs.py | 3 ++ PyDC/PyDC/tests.py | 81 ++++++++++++++++++++++++++++++++---- PyDC/PyDC_cli.py | 10 +++++ PyDC/README.creole | 19 +++++++++ 5 files changed, 110 insertions(+), 9 deletions(-) diff --git a/PyDC/PyDC/CassetteObjects.py b/PyDC/PyDC/CassetteObjects.py index ce5d99e2..3b954ffa 100644 --- a/PyDC/PyDC/CassetteObjects.py +++ b/PyDC/PyDC/CassetteObjects.py @@ -58,6 +58,10 @@ def create_from_bas(self, file_content): for line in file_content.splitlines(): line_number, code = line.split(" ", 1) line_number = int(line_number) + + if self.cfg.case_convert: + code = code.upper() + self.code_lines.append( CodeLine(None, line_number, code) ) @@ -632,6 +636,8 @@ def write_bas(self, destination_file): log.info("Create %s..." % repr(out_filename)) with open(out_filename, "w") as f: for line in file_obj.file_content.get_ascii_codeline(): + if self.cfg.case_convert: + line = line.lower() f.write("%s\n" % line) print "\nFile %s saved." % repr(out_filename) diff --git a/PyDC/PyDC/configs.py b/PyDC/PyDC/configs.py index 36578be5..924b44a4 100644 --- a/PyDC/PyDC/configs.py +++ b/PyDC/PyDC/configs.py @@ -121,6 +121,9 @@ class Dragon32Config(BaseConfig): NO_GAPS = 0x00 GAPS = 0xff + # Convert to uppercase if source is .bas and to lowercase if destination is .bas + case_convert = False + if __name__ == "__main__": import doctest diff --git a/PyDC/PyDC/tests.py b/PyDC/PyDC/tests.py index c5edb60b..9fd46f39 100755 --- a/PyDC/PyDC/tests.py +++ b/PyDC/PyDC/tests.py @@ -9,16 +9,17 @@ :license: GNU GPL v3 or above, see LICENSE for more details. """ +import itertools import logging import os import sys +import tempfile import unittest -import itertools # own modules from __init__ import convert -import configs from wave2bitstream import Wave2Bitstream +import configs class TestDragon32Conversion(unittest.TestCase): @@ -31,9 +32,24 @@ def setUp(self): ) self.cfg = configs.Dragon32Config() + self.temp_files = [] + def tearDown(self): print "\n"*2 print " >>>unittest tearDown() >>>", + for filename in self.temp_files: + if os.path.exists(filename): + try: + os.remove(filename) + except Exception, err: + print "Error remove temp file: %s" % err + + self.temp_files = [] + + def _get_named_temp_file(self, *args, **kwargs): + f = tempfile.NamedTemporaryFile(*args, **kwargs) + self.temp_files.append(f.name) + return f def _src_file_path(self, filename): return os.path.relpath( @@ -190,17 +206,18 @@ def test_bas2cas01(self): def test_cas01(self): # create cas source_filepath = self._src_file_path("LineNumberTest.bas") - cas_filepath = self._dst_file_path("unittest_LineNumberTest.cas") - convert(source_filepath, cas_filepath, self.cfg) + cas_file = self._get_named_temp_file( + prefix="test_cas01", suffix=".cas", delete=False + ) - # create bas from created cas file - destination_filepath = self._dst_file_path("unittest_LineNumberTest.bas") - convert(cas_filepath, destination_filepath, self.cfg) + convert(source_filepath, cas_file.name, self.cfg) - os.remove(cas_filepath) + # create bas from created cas file + destination_filepath = self._dst_file_path("unittest_cas01.bas") + convert(cas_file.name, destination_filepath, self.cfg) # filename 'LINENUMB' used in CSAVE: - destination_filepath = self._dst_file_path("unittest_LineNumberTest_LINENUMB.bas") + destination_filepath = self._dst_file_path("unittest_cas01_LINENUMB.bas") dest_content = self._get_and_delete_dst(destination_filepath) self.assertMultiLineEqual(dest_content, ( @@ -238,6 +255,50 @@ def test_bas2ascii_wav(self): '30 NEXT I\n' )) + def test_case_convert01(self): + """ + UPPERCASE from wave to lowercase .bas + """ + source_filepath = self._src_file_path("HelloWorld1 xroar.wav") + destination_filepath = self._dst_file_path("unittest_case_convert01.bas") + + cfg = configs.Dragon32Config() + cfg.case_convert = True + convert(source_filepath, destination_filepath, cfg) + + # no filename used in CSAVE: + destination_filepath = self._dst_file_path("unittest_case_convert01_.bas") + + dest_content = self._get_and_delete_dst(destination_filepath) + + lowcase_content = ( + '10 for i = 1 to 10\n' + '20 print i;"hello world!"\n' + '30 next i\n' + ) + self.assertMultiLineEqual(dest_content, lowcase_content) + + def test_case_convert02(self): + """ + lowercase from .bas to UPPERCASE .cas + """ + source = self._get_named_temp_file(suffix=".bas", delete=False) + source.write('10 print "lowercase?"') + source.close() + dest = self._get_named_temp_file(suffix=".cas", delete=False) + dest.close() + + cfg = configs.Dragon32Config() + cfg.case_convert = True + convert(source.name, dest.name, cfg) + + dest_content = self._get_and_delete_dst(dest.name) + + dest_content = dest_content.replace("U", "") # "remove" LeadInByte + + self.assertIn('\r10 PRINT "LOWERCASE?"\r' , dest_content) + + def test_more_than_one_code_block(self): """ TODO: Test if wav/cas has more than 256Bytes code (code in more than one block) @@ -265,6 +326,8 @@ def test_more_than_one_code_block(self): # "TestDragon32Conversion.test_bas2cas01", # "TestDragon32Conversion.test_cas01", # "TestDragon32Conversion.test_bas2ascii_wav", +# "TestDragon32Conversion.test_case_convert01", +# "TestDragon32Conversion.test_case_convert02", ), # verbosity=1, verbosity=2, diff --git a/PyDC/PyDC_cli.py b/PyDC/PyDC_cli.py index ad7a2eea..674b4ae9 100755 --- a/PyDC/PyDC_cli.py +++ b/PyDC/PyDC_cli.py @@ -93,6 +93,14 @@ def __init__(self): ) % self.cfg.MID_COUNT ) + self.parser.add_argument( + "--case_convert", action="store_true", + help=( + "Convert to uppercase if source is .bas" + " and to lowercase if destination is .bas" + ) + ) + def parse_args(self): args = super(PyDC_CLI, self).parse_args() @@ -127,6 +135,8 @@ def run(self): self.cfg.END_COUNT = self.args.end_count # Sample count that must be pos/neg at once self.cfg.MID_COUNT = self.args.mid_count # Sample count that can be around null + self.cfg.case_convert = self.args.case_convert + if self.args.analyze: analyze(self.source_file, self.cfg) else: diff --git a/PyDC/README.creole b/PyDC/README.creole index 8a135bb2..72a6a6a1 100644 --- a/PyDC/README.creole +++ b/PyDC/README.creole @@ -58,6 +58,8 @@ optional arguments: Sample count that must be pos/neg at once (default: 2) --mid_count MID_COUNT Sample count that can be around null (default: 1) + --case_convert Convert to uppercase if source is .bas and to + lowercase if destination is .bas PyDC v0.1.0.dev copyleft 2013 by htfx.de - Jens Diemer, GNU GPL v3 or above @@ -106,6 +108,23 @@ Some statistics from WAVES files are here: * http://archive.worldofdragon.org/phpBB3/viewtopic.php?f=8&t=4326 +==== case convertion + +With {{{--case_convert}}} you can convert upper and lower case: +# if source is a {{.bas}} file: All cased characters converted to lowercase +# if destination is {{.cas}} or {{.wav}}: All cased characters converted to uppercase + +So you can write a local {{{.bas}}} files on pc in "normal" way, like this: +{{{ +10 print "hello world!" +}}} +With {{{--case_convert}}} the content will be saved in {{.cas}} or {{.wav}} so: +{{{ +10 PRINT "HELLO WORLD!" +}}} +Other way around, vice versa. + + === TODO * add tokenized BASIC output, too. Currently {{{.wav}}} and {{{.cas}}} would be always output as ASCII BASIC. From 995a1ac17301c3a34875012691512796ea0f1b11 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Mon, 9 Sep 2013 18:40:55 +0200 Subject: [PATCH 084/151] split output in blocks with a max size of 255Bytes --- PyDC/PyDC/CassetteObjects.py | 65 ++++++++++++++++++++++-------------- PyDC/README.creole | 1 - 2 files changed, 40 insertions(+), 26 deletions(-) diff --git a/PyDC/PyDC/CassetteObjects.py b/PyDC/PyDC/CassetteObjects.py index 3b954ffa..d75762c9 100644 --- a/PyDC/PyDC/CassetteObjects.py +++ b/PyDC/PyDC/CassetteObjects.py @@ -38,7 +38,7 @@ def get_ascii_codeline(self): return "%i %s" % (self.line_no, self.code) def get_as_codepoints(self): - return string2codepoint(self.get_ascii_codeline()) + return tuple(string2codepoint(self.get_ascii_codeline())) def __repr__(self): return "" % ( @@ -262,7 +262,6 @@ def get_as_codepoints(self): result.append(delim) result += list(code_line.get_as_codepoints()) result.append(delim) - return result def get_ascii_codeline(self): @@ -584,14 +583,21 @@ def codepoint_stream(self): self.wav.write_silence(sec=0.1) # yield file content - for codepoints in self.block2codepoint_stream( - block_type=self.cfg.DATA_BLOCK, - block_codepoints=file_obj.get_code_block_as_codepoints() - ): - yield codepoints + codepoints = tuple(file_obj.get_code_block_as_codepoints()) + codepoints = iter(codepoints) + while True: + raw_codepoints = tuple(itertools.islice(codepoints, 0, 255)) + if not raw_codepoints: + break + # Add meta information + codepoint_stream = self.block2codepoint_stream( + block_type=self.cfg.DATA_BLOCK, block_codepoints=raw_codepoints + ) + for codepoints2 in codepoint_stream: + yield codepoints2 - if self.wav: - self.wav.write_silence(sec=0.1) + if self.wav: + self.wav.write_silence(sec=0.1) # yield EOF for codepoints in self.block2codepoint_stream( @@ -617,13 +623,22 @@ def write_wave(self, destination_file): def write_cas(self, destination_file): log.info("Create %s..." % repr(destination_file)) + + def _write(f, codepoint): + try: + f.write(chr(codepoint)) + except ValueError, err: + log.error("Value error with %s: %s" % (repr(codepoint), err)) + raise + with open(destination_file, "wb") as f: for codepoint in self.codepoint_stream(): if isinstance(codepoint, (tuple, list)): for item in codepoint: - f.write(chr(item)) + _write(f, item) else: - f.write(chr(codepoint)) + _write(f, codepoint) + print "\nFile %s saved." % repr(destination_file) def write_bas(self, destination_file): @@ -670,28 +685,28 @@ def pprint_codepoint_stream(self): import subprocess # bas -> wav -# subprocess.Popen([sys.executable, "../PyDC_cli.py", + subprocess.Popen([sys.executable, "../PyDC_cli.py", # "--verbosity=10", -# # "--verbosity=5", -# # "--logfile=5", -# # "--log_format=%(module)s %(lineno)d: %(message)s", + "--verbosity=5", +# "--logfile=5", +# "--log_format=%(module)s %(lineno)d: %(message)s", # "../test_files/HelloWorld1.bas", "--dst=../test.wav" -# # "../test_files/HelloWorld1.bas", "--dst=../test.cas" -# ]).wait() + "../test_files/HelloWorld1.bas", "--dst=../test.cas" + ]).wait() # print "\n"*3 # print "="*79 # print "\n"*3 # # # # wav -> bas - subprocess.Popen([sys.executable, "../PyDC_cli.py", -# "--verbosity=10", - "--verbosity=7", -# "../test.wav", "--dst=../test.bas", -# "../test.cas", "--dst=../test.bas", -# "../test_files/HelloWorld1 origin.wav", "--dst=../test_files/HelloWorld1.bas", - "../test_files/LineNumber Test 02.wav", "--dst=../test.bas", - ]).wait() +# subprocess.Popen([sys.executable, "../PyDC_cli.py", +# # "--verbosity=10", +# "--verbosity=7", +# # "../test.wav", "--dst=../test.bas", +# # "../test.cas", "--dst=../test.bas", +# # "../test_files/HelloWorld1 origin.wav", "--dst=../test_files/HelloWorld1.bas", +# "../test_files/LineNumber Test 02.wav", "--dst=../test.bas", +# ]).wait() # # print "-- END --" diff --git a/PyDC/README.creole b/PyDC/README.creole index 72a6a6a1..968674d6 100644 --- a/PyDC/README.creole +++ b/PyDC/README.creole @@ -128,7 +128,6 @@ Other way around, vice versa. === TODO * add tokenized BASIC output, too. Currently {{{.wav}}} and {{{.cas}}} would be always output as ASCII BASIC. -* support for longer listing as 255 characters. Currently the blocks would not be limited to 255Bytes ;( === Links From c0a6df855f1940b70e4bbfec8212d3d9cb43e962 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Fri, 4 Oct 2013 23:08:05 +0200 Subject: [PATCH 085/151] start a simple memory hex viewer --- HexViewer/hex_view01.bas | 44 ++++++++++++++++++++++++++++++++++++++++ README.creole | 13 ++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 HexViewer/hex_view01.bas diff --git a/HexViewer/hex_view01.bas b/HexViewer/hex_view01.bas new file mode 100644 index 00000000..b5cdff60 --- /dev/null +++ b/HexViewer/hex_view01.bas @@ -0,0 +1,44 @@ +1 GOTO 10005 +10 ' GO +20 TXT$ = RIGHT$("000"+HEX$(EA),4) + " " +30 CHARS$ = "" +40 FOR I = 0 TO 5 +50 EA=EA+I +60 V=PEEK(EA) +70 TXT$ = TXT$ + " " + RIGHT$("0"+HEX$(V),2) +80 IF V=>33 AND V<=122 THEN CHARS$=CHARS$+CHR$(V) ELSE CHARS$=CHARS$+"." +90 NEXT I +100 TXT$ = TXT$ +" " + CHARS$ +110 PRINT TXT$ +120 RETURN +10005 CLS +10010 PRINT "HEX VIEWER V0.1 GPL V3 OR ABOVE" +10020 PRINT "COPYLEFT (C) 2013 JENS DIEMER" +10030 PRINT +10040 PRINT "PLEASE SELECT:" +10050 PRINT " (S)INGLE MEM ADDRESS AS HEX" +10060 PRINT " (L)INE OF MEMORY" +10070 PRINT " (W)INDOWED HEX VIEW" +10100 MODE$ = INKEY$:IF MODE$="" THEN 10100 +10110 IF MODE$="S" THEN 10200 +10120 IF MODE$="L" THEN 10500 +10130 IF MODE$="W" THEN 11000 +10140 GOTO 10005 +10200 INPUT "MEM ADDRESS IN HEX &H"; ADDR$ +10205 ADDR = VAL("&H"+ADDR$) +10210 PRINT "PEEK VALUE FROM $";HEX$(ADDR);": $";HEX$(PEEK(ADDR)) +10220 GOTO 10200 +10500 INPUT "MEM ADDRESS IN HEX &H"; ADDR$ +10510 EA = VAL("&H"+ADDR$) +10515 IF EA=0 AND ADDR$<>"0" THEN 10005 +10520 GOSUB 10 +10530 GOTO 10500 +11000 INPUT "START ADDRESS IN HEX &H"; ADDR$ +11010 EA = VAL("&H"+ADDR$) +11015 IF EA=0 AND ADDR$<>"0" THEN 10005 +11020 FOR L = 0 TO 6 +11030 EA = EA + 6 * L +11040 GOSUB 10 +11050 NEXT L +11060 GOTO 11000 + diff --git a/README.creole b/README.creole index c2e4d8ab..5a8a12f7 100644 --- a/README.creole +++ b/README.creole @@ -4,3 +4,16 @@ Some Python tools/script around Dragon32/64 homecomputer: * PyDC - Convert dragon 32 Cassetts WAV files into plain text: ** https://github.com/jedie/PyDragon32/tree/master/PyDC + +* Simple memory HEX viewer written in BASIC: +** https://github.com/jedie/PyDragon32/tree/master/HexViewer + += donation + +* Send [[http://www.bitcoin.org/|Bitcoins]] to [[https://blockexplorer.com/address/1823RZ5Md1Q2X5aSXRC5LRPcYdveCiVX6F|1823RZ5Md1Q2X5aSXRC5LRPcYdveCiVX6F]] + +== Links: + +| Forum | [[http://forum.pylucid.org/]] +| IRC | [[http://www.pylucid.org/permalink/304/irc-channel|#pylucid on freenode.net]] +| Github | [[http://github.com/jedie/PyLucid]] \ No newline at end of file From 4677f8fadef38aa9e7956e6152e099e9d3731e3d Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Fri, 4 Oct 2013 23:12:50 +0200 Subject: [PATCH 086/151] update README --- HexViewer/README.creole | 7 +++++++ README.creole | 4 +++- 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 HexViewer/README.creole diff --git a/HexViewer/README.creole b/HexViewer/README.creole new file mode 100644 index 00000000..b3ac29fc --- /dev/null +++ b/HexViewer/README.creole @@ -0,0 +1,7 @@ + +Simple memory HEX viewer written in BASIC + +{{http://www.jensdiemer.de/media/jensdiemer.de/screenshots/hex_view_v01.png|hex_view_v01.png}} + +copyleft: 2013 by Jens Diemer +license: GNU GPL v3 or above, see LICENSE for more details. \ No newline at end of file diff --git a/README.creole b/README.creole index 5a8a12f7..868db36f 100644 --- a/README.creole +++ b/README.creole @@ -1,6 +1,6 @@ == PyDragon32 -Some Python tools/script around Dragon32/64 homecomputer: +Some Python/BASIC tools/script around Dragon32/64 homecomputer: * PyDC - Convert dragon 32 Cassetts WAV files into plain text: ** https://github.com/jedie/PyDragon32/tree/master/PyDC @@ -8,6 +8,8 @@ Some Python tools/script around Dragon32/64 homecomputer: * Simple memory HEX viewer written in BASIC: ** https://github.com/jedie/PyDragon32/tree/master/HexViewer +All script are copyleft 2013 by Jens Diemer and license unter GNU GPL v3 or above, see LICENSE for more details. + = donation * Send [[http://www.bitcoin.org/|Bitcoins]] to [[https://blockexplorer.com/address/1823RZ5Md1Q2X5aSXRC5LRPcYdveCiVX6F|1823RZ5Md1Q2X5aSXRC5LRPcYdveCiVX6F]] From 596e11f971598e4114be754f2b63a5c87a0cf809 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Fri, 11 Oct 2013 17:35:04 +0200 Subject: [PATCH 087/151] Add BASIC programm: Test CC Registers --- README.creole | 17 +++++++--- TestCC_Registers/README.creole | 15 +++++++++ TestCC_Registers/testCC.bas | 58 ++++++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 4 deletions(-) mode change 100644 => 100755 README.creole create mode 100755 TestCC_Registers/README.creole create mode 100755 TestCC_Registers/testCC.bas diff --git a/README.creole b/README.creole old mode 100644 new mode 100755 index 868db36f..34dc47b1 --- a/README.creole +++ b/README.creole @@ -1,14 +1,23 @@ == PyDragon32 -Some Python/BASIC tools/script around Dragon32/64 homecomputer: +Some Python/BASIC tools/scripts around Dragon32/64 / CoCo homecomputer. + +All script are copyleft 2013 by Jens Diemer and license unter GNU GPL v3 or above, see LICENSE for more details. + +=== Python scripts: * PyDC - Convert dragon 32 Cassetts WAV files into plain text: ** https://github.com/jedie/PyDragon32/tree/master/PyDC -* Simple memory HEX viewer written in BASIC: -** https://github.com/jedie/PyDragon32/tree/master/HexViewer +=== BASIC programms: + +* Simple memory HEX viewer: +** https://github.com/jedie/PyDragon32/tree/master/HexViewer + +* Test CC Registers: +** https://github.com/jedie/PyDragon32/tree/master/TestCC_Registers + -All script are copyleft 2013 by Jens Diemer and license unter GNU GPL v3 or above, see LICENSE for more details. = donation diff --git a/TestCC_Registers/README.creole b/TestCC_Registers/README.creole new file mode 100755 index 00000000..b48e2488 --- /dev/null +++ b/TestCC_Registers/README.creole @@ -0,0 +1,15 @@ + +=== Test CC Registers + +A BASIC programm with inserted machine code to diplay the CC registers of the CPU. + + + + +copyleft: 2013 by Jens Diemer +license: GNU GPL v3 or above, see LICENSE for more details. + + + +Many thanks to the people from: + * [[http://archive.worldofdragon.org/phpBB3/| World Of Dragon - Forum]] diff --git a/TestCC_Registers/testCC.bas b/TestCC_Registers/testCC.bas new file mode 100755 index 00000000..df4a2552 --- /dev/null +++ b/TestCC_Registers/testCC.bas @@ -0,0 +1,58 @@ +1 PRINT:PRINT "TEST CC REGISTERS V0.1" +2 PRINT "(GPL V3 OR ABOVE)" +3 PRINT:PRINT "COPYLEFT (C) 2013 JENS DIEMER":PRINT +11 COUNT=14 +20 LA=&H4000 ' LOAD / EXECUTE ADDRESS +25 PRINT "POKE MACHINE CODE TO: $";HEX$(LA) +30 PA = LA ' START ADDRESS FOR POKE +50 READ HB$ ' HEX CONSTANTS +60 IF HB$="END" THEN 100 +65 V=VAL("&H"+HB$) +70 POKE PA,V ' POKE VALUE INTO MEMORY +75 'PRINT "POKE $";HEX$(V);" AT $";HEX$(PA) +80 PA = PA + 1 ' INCREMENT POKE ADDRESS +90 GOTO 50 +100 PRINT "LOADED, END ADDRESS IS: $"; HEX$(PA-1) +110 PRINT:INPUT "INPUT START VALUE (DEZ)";A$ +115 IF A$="" THEN 20000 ELSE A=VAL(A$) +120 A=A-1 +130 GOTO 500 +140 PRINT "UP/DOWN OR ANYKEY FOR NEW VALUE"; +150 I$ = INKEY$:IF I$="" THEN 150 +160 IF I$=CHR$(&H5E) THEN A=A-(COUNT*2):GOTO 500 ' UP KEYPRESS +170 IF I$=CHR$(&H0A) THEN 500 ' DOWN KEYPRESS +180 GOTO 110 ' NOT UP/DOWN +500 A=A AND &HFF ' WRAP AROUND +510 POKE &H4500,A ' SET START VALUE +520 'PRINT "A=";A;" VALUE FROM $4500: ";PEEK(&H4500) +540 PRINT " EFHINZVC" +550 FOR I = 1 TO COUNT +560 EXEC LA +570 CC=PEEK(&H4501) ' CC-REGISTER +580 A=PEEK(&H4500) ' ACCU A +590 ' CREATE BITS +600 T = CC +610 B7$="0":IF T AND 128 THEN B7$="1" +620 B6$="0":IF T AND 64 THEN B6$="1" +630 B5$="0":IF T AND 32 THEN B5$="1" +640 B4$="0":IF T AND 16 THEN B4$="1" +650 B3$="0":IF T AND 8 THEN B3$="1" +660 B2$="0":IF T AND 4 THEN B2$="1" +670 B1$="0":IF T AND 2 THEN B1$="1" +680 B0$="0":IF T AND 1 THEN B1$="1" +690 PRINT "A=";RIGHT$(" "+STR$(A),4);" CC=$";HEX$(CC);":";B7$;B6$;B5$;B4$;B3$;B2$;B1$;B0$ +700 NEXT I +710 GOTO 140 +1000 ' MACHINE CODE IN HEX +1009 ' LDA $4500 +1010 DATA B6,45,00 +1019 ' ADDA 1 +1020 DATA 8B,01 +1029 ' TFR CC,B +1030 DATA 1F,A9 +1039 ' STD $4500 ; STORE A+B +1040 DATA FD,45,00 +1049 ' RTS +1050 DATA 39 +10000 DATA END +20000 PRINT:PRINT "BYE" From 432166274cc7986f2ba71d748f840f57afd91e95 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Fri, 11 Oct 2013 17:39:22 +0200 Subject: [PATCH 088/151] add screenshots --- TestCC_Registers/README.creole | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/TestCC_Registers/README.creole b/TestCC_Registers/README.creole index b48e2488..fb075af1 100755 --- a/TestCC_Registers/README.creole +++ b/TestCC_Registers/README.creole @@ -3,13 +3,14 @@ A BASIC programm with inserted machine code to diplay the CC registers of the CPU. - - - copyleft: 2013 by Jens Diemer license: GNU GPL v3 or above, see LICENSE for more details. +{{http://www.jensdiemer.de/media/jensdiemer.de/screenshots/test_cc_register01.PNG|test_cc_register01.PNG}} + +{{http://www.jensdiemer.de/media/jensdiemer.de/screenshots/test_cc_register02.PNG|test_cc_register02.PNG}} + Many thanks to the people from: * [[http://archive.worldofdragon.org/phpBB3/| World Of Dragon - Forum]] From ffe232fabc44af049767ae2606bc025bd563d5db Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Fri, 11 Oct 2013 18:01:38 +0200 Subject: [PATCH 089/151] "overwrite" prints are faster and nicer ;) --- TestCC_Registers/testCC.bas | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TestCC_Registers/testCC.bas b/TestCC_Registers/testCC.bas index df4a2552..8f4811d3 100755 --- a/TestCC_Registers/testCC.bas +++ b/TestCC_Registers/testCC.bas @@ -25,7 +25,7 @@ 500 A=A AND &HFF ' WRAP AROUND 510 POKE &H4500,A ' SET START VALUE 520 'PRINT "A=";A;" VALUE FROM $4500: ";PEEK(&H4500) -540 PRINT " EFHINZVC" +540 PRINT @ 0, " EFHINZVC" 550 FOR I = 1 TO COUNT 560 EXEC LA 570 CC=PEEK(&H4501) ' CC-REGISTER @@ -40,7 +40,7 @@ 660 B2$="0":IF T AND 4 THEN B2$="1" 670 B1$="0":IF T AND 2 THEN B1$="1" 680 B0$="0":IF T AND 1 THEN B1$="1" -690 PRINT "A=";RIGHT$(" "+STR$(A),4);" CC=$";HEX$(CC);":";B7$;B6$;B5$;B4$;B3$;B2$;B1$;B0$ +690 PRINT "A=";RIGHT$(" "+STR$(A),3);" CC=$";HEX$(CC);":";B7$;B6$;B5$;B4$;B3$;B2$;B1$;B0$ 700 NEXT I 710 GOTO 140 1000 ' MACHINE CODE IN HEX From 3bbbf856da1967aceebf9fd63217277955c86793 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Fri, 11 Oct 2013 22:53:37 +0200 Subject: [PATCH 090/151] add two more CC test files --- TestCC_Registers/README.creole | 83 ++++++++++++++++++++++++++++++++ TestCC_Registers/testCC_ADDA.bas | 78 ++++++++++++++++++++++++++++++ TestCC_Registers/testCC_SUBA.bas | 78 ++++++++++++++++++++++++++++++ 3 files changed, 239 insertions(+) create mode 100644 TestCC_Registers/testCC_ADDA.bas create mode 100644 TestCC_Registers/testCC_SUBA.bas diff --git a/TestCC_Registers/README.creole b/TestCC_Registers/README.creole index fb075af1..183b76fd 100755 --- a/TestCC_Registers/README.creole +++ b/TestCC_Registers/README.creole @@ -12,5 +12,88 @@ license: GNU GPL v3 or above, see LICENSE for more details. {{http://www.jensdiemer.de/media/jensdiemer.de/screenshots/test_cc_register02.PNG|test_cc_register02.PNG}} +=== test + +XRoar trace output machine code part from **testCC_ADDA.bas** (except first JMP line): +{{{ +b777| 6e9f009d JMP [$009D] cc=a0 a=00 b=00 dp=00 x=4000 y=890b u=203d s=7f33 +4000| b64500 LDA $4500 cc=a8 a=fc b=00 dp=00 x=4000 y=890b u=203d s=7f33 +4003| 8b01 ADDA #$01 cc=88 a=fd b=00 dp=00 x=4000 y=890b u=203d s=7f33 +4005| 1fa9 TFR CC,B cc=88 a=fd b=88 dp=00 x=4000 y=890b u=203d s=7f33 +4007| fd4502 STD $4502 cc=88 a=fd b=88 dp=00 x=4000 y=890b u=203d s=7f33 +400a| b64500 LDA $4500 cc=88 a=fc b=88 dp=00 x=4000 y=890b u=203d s=7f33 +400d| 8b02 ADDA #$02 cc=88 a=fe b=88 dp=00 x=4000 y=890b u=203d s=7f33 +400f| 1fa9 TFR CC,B cc=88 a=fe b=88 dp=00 x=4000 y=890b u=203d s=7f33 +4011| fd4504 STD $4504 cc=88 a=fe b=88 dp=00 x=4000 y=890b u=203d s=7f33 +4014| b64500 LDA $4500 cc=88 a=fc b=88 dp=00 x=4000 y=890b u=203d s=7f33 +4017| 8b03 ADDA #$03 cc=88 a=ff b=88 dp=00 x=4000 y=890b u=203d s=7f33 +4019| 1fa9 TFR CC,B cc=88 a=ff b=88 dp=00 x=4000 y=890b u=203d s=7f33 +401b| fd4506 STD $4506 cc=88 a=ff b=88 dp=00 x=4000 y=890b u=203d s=7f33 +401e| b64500 LDA $4500 cc=88 a=fc b=88 dp=00 x=4000 y=890b u=203d s=7f33 +4021| 8b04 ADDA #$04 cc=a5 a=00 b=88 dp=00 x=4000 y=890b u=203d s=7f33 +4023| 1fa9 TFR CC,B cc=a5 a=00 b=a5 dp=00 x=4000 y=890b u=203d s=7f33 +4025| fd4508 STD $4508 cc=a1 a=00 b=a5 dp=00 x=4000 y=890b u=203d s=7f33 +4028| b64500 LDA $4500 cc=a9 a=fc b=a5 dp=00 x=4000 y=890b u=203d s=7f33 +402b| 8b05 ADDA #$05 cc=a1 a=01 b=a5 dp=00 x=4000 y=890b u=203d s=7f33 +402d| 1fa9 TFR CC,B cc=a1 a=01 b=a1 dp=00 x=4000 y=890b u=203d s=7f33 +402f| fd450a STD $450a cc=a1 a=01 b=a1 dp=00 x=4000 y=890b u=203d s=7f33 +4032| b64500 LDA $4500 cc=a9 a=fc b=a1 dp=00 x=4000 y=890b u=203d s=7f33 +4035| 8b06 ADDA #$06 cc=a1 a=02 b=a1 dp=00 x=4000 y=890b u=203d s=7f33 +4037| 1fa9 TFR CC,B cc=a1 a=02 b=a1 dp=00 x=4000 y=890b u=203d s=7f33 +4039| fd450c STD $450c cc=a1 a=02 b=a1 dp=00 x=4000 y=890b u=203d s=7f33 +403c| b64500 LDA $4500 cc=a9 a=fc b=a1 dp=00 x=4000 y=890b u=203d s=7f33 +403f| 8b07 ADDA #$07 cc=a1 a=03 b=a1 dp=00 x=4000 y=890b u=203d s=7f33 +4041| 1fa9 TFR CC,B cc=a1 a=03 b=a1 dp=00 x=4000 y=890b u=203d s=7f33 +4043| fd450e STD $450e cc=a1 a=03 b=a1 dp=00 x=4000 y=890b u=203d s=7f33 +4046| b64500 LDA $4500 cc=a9 a=fc b=a1 dp=00 x=4000 y=890b u=203d s=7f33 +4049| 8b08 ADDA #$08 cc=a1 a=04 b=a1 dp=00 x=4000 y=890b u=203d s=7f33 +404b| 1fa9 TFR CC,B cc=a1 a=04 b=a1 dp=00 x=4000 y=890b u=203d s=7f33 +404d| fd4510 STD $4510 cc=a1 a=04 b=a1 dp=00 x=4000 y=890b u=203d s=7f33 +4050| 39 RTS cc=a1 a=04 b=a1 dp=00 x=4000 y=890b u=203d s=7f35 +84da| 20c3 BRA $849f cc=a1 a=04 b=a1 dp=00 x=4000 y=890b u=203d s=7f35 +849f| bd019a JSR $019a cc=a1 a=04 b=a1 dp=00 x=4000 y=890b u=203d s=7f33 +019a| 39 RTS cc=a1 a=04 b=a1 dp=00 x=4000 y=890b u=203d s=7f35 +}}} + + +XRoar trace output machine code part from **testCC_SUBA.bas** (except first JMP line): +{{{ +b777| 6e9f009d JMP [$009D] cc=a0 a=00 b=00 dp=00 x=4000 y=890b u=2037 s=7f33 +4000| b64500 LDA $4500 cc=a0 a=03 b=00 dp=00 x=4000 y=890b u=2037 s=7f33 +4003| 8001 SUBA #$01 cc=a0 a=02 b=00 dp=00 x=4000 y=890b u=2037 s=7f33 +4005| 1fa9 TFR CC,B cc=a0 a=02 b=a0 dp=00 x=4000 y=890b u=2037 s=7f33 +4007| fd4502 STD $4502 cc=a0 a=02 b=a0 dp=00 x=4000 y=890b u=2037 s=7f33 +400a| b64500 LDA $4500 cc=a0 a=03 b=a0 dp=00 x=4000 y=890b u=2037 s=7f33 +400d| 8002 SUBA #$02 cc=a0 a=01 b=a0 dp=00 x=4000 y=890b u=2037 s=7f33 +400f| 1fa9 TFR CC,B cc=a0 a=01 b=a0 dp=00 x=4000 y=890b u=2037 s=7f33 +4011| fd4504 STD $4504 cc=a0 a=01 b=a0 dp=00 x=4000 y=890b u=2037 s=7f33 +4014| b64500 LDA $4500 cc=a0 a=03 b=a0 dp=00 x=4000 y=890b u=2037 s=7f33 +4017| 8003 SUBA #$03 cc=a4 a=00 b=a0 dp=00 x=4000 y=890b u=2037 s=7f33 +4019| 1fa9 TFR CC,B cc=a4 a=00 b=a4 dp=00 x=4000 y=890b u=2037 s=7f33 +401b| fd4506 STD $4506 cc=a0 a=00 b=a4 dp=00 x=4000 y=890b u=2037 s=7f33 +401e| b64500 LDA $4500 cc=a0 a=03 b=a4 dp=00 x=4000 y=890b u=2037 s=7f33 +4021| 8004 SUBA #$04 cc=a9 a=ff b=a4 dp=00 x=4000 y=890b u=2037 s=7f33 +4023| 1fa9 TFR CC,B cc=a9 a=ff b=a9 dp=00 x=4000 y=890b u=2037 s=7f33 +4025| fd4508 STD $4508 cc=a9 a=ff b=a9 dp=00 x=4000 y=890b u=2037 s=7f33 +4028| b64500 LDA $4500 cc=a1 a=03 b=a9 dp=00 x=4000 y=890b u=2037 s=7f33 +402b| 8005 SUBA #$05 cc=a9 a=fe b=a9 dp=00 x=4000 y=890b u=2037 s=7f33 +402d| 1fa9 TFR CC,B cc=a9 a=fe b=a9 dp=00 x=4000 y=890b u=2037 s=7f33 +402f| fd450a STD $450a cc=a9 a=fe b=a9 dp=00 x=4000 y=890b u=2037 s=7f33 +4032| b64500 LDA $4500 cc=a1 a=03 b=a9 dp=00 x=4000 y=890b u=2037 s=7f33 +4035| 8006 SUBA #$06 cc=a9 a=fd b=a9 dp=00 x=4000 y=890b u=2037 s=7f33 +4037| 1fa9 TFR CC,B cc=a9 a=fd b=a9 dp=00 x=4000 y=890b u=2037 s=7f33 +4039| fd450c STD $450c cc=a9 a=fd b=a9 dp=00 x=4000 y=890b u=2037 s=7f33 +403c| b64500 LDA $4500 cc=a1 a=03 b=a9 dp=00 x=4000 y=890b u=2037 s=7f33 +403f| 8007 SUBA #$07 cc=a9 a=fc b=a9 dp=00 x=4000 y=890b u=2037 s=7f33 +4041| 1fa9 TFR CC,B cc=a9 a=fc b=a9 dp=00 x=4000 y=890b u=2037 s=7f33 +4043| fd450e STD $450e cc=a9 a=fc b=a9 dp=00 x=4000 y=890b u=2037 s=7f33 +4046| b64500 LDA $4500 cc=a1 a=03 b=a9 dp=00 x=4000 y=890b u=2037 s=7f33 +4049| 8008 SUBA #$08 cc=a9 a=fb b=a9 dp=00 x=4000 y=890b u=2037 s=7f33 +404b| 1fa9 TFR CC,B cc=a9 a=fb b=a9 dp=00 x=4000 y=890b u=2037 s=7f33 +404d| fd4510 STD $4510 cc=a9 a=fb b=a9 dp=00 x=4000 y=890b u=2037 s=7f33 +4050| 39 RTS cc=a9 a=fb b=a9 dp=00 x=4000 y=890b u=2037 s=7f35 +}}} + + Many thanks to the people from: * [[http://archive.worldofdragon.org/phpBB3/| World Of Dragon - Forum]] diff --git a/TestCC_Registers/testCC_ADDA.bas b/TestCC_Registers/testCC_ADDA.bas new file mode 100644 index 00000000..ba900d8c --- /dev/null +++ b/TestCC_Registers/testCC_ADDA.bas @@ -0,0 +1,78 @@ +10 PRINT:PRINT "TEST CC ADDA V0.1" +20 PRINT "(GPL V3 OR ABOVE)" +30 PRINT:PRINT "COPYLEFT (C) 2013 JENS DIEMER":PRINT +40 LA=&H4000 ' LOAD / EXECUTE ADDRESS +50 PRINT "POKE MACHINE CODE TO: $";HEX$(LA) +60 PA = LA ' START ADDRESS FOR POKE +70 READ HB$ ' HEX CONSTANTS +80 IF HB$="END" THEN 140 +90 V=VAL("&H"+HB$) +100 POKE PA,V ' POKE VALUE INTO MEMORY +110 'PRINT "POKE $";HEX$(V);" AT $";HEX$(PA) +120 PA = PA + 1 ' INCREMENT POKE ADDRESS +130 GOTO 70 +140 PRINT "LOADED, END ADDRESS IS: $"; HEX$(PA-1) +150 INIT=252:PRINT +160 PRINT "INIT ACCU A WITH";INIT +170 POKE &H4500,INIT ' SET START VALUE +180 PRINT "START MACHINE CODE PROGRAMM" +190 EXEC LA +200 PRINT "RESULTS:" +210 EA=&H4500 +220 FOR I = 1 TO 8 +230 PRINT I;INIT;"A:";PEEK(EA);" CC:";HEX$(PEEK(EA+1)) +235 INIT=INIT+1 +240 EA=EA+2 +250 NEXT I +270 ' MACHINE CODE IN HEX +280 ' LDA $4500 +290 DATA B6,45,00 +300 ' ADDA 1 +310 DATA 8B,01 +320 ' TFR CC,B + STD $4502 +330 DATA 1F,A9,FD,45,02 +340 ' LDA $4500 +350 DATA B6,45,00 +360 ' ADDA 2 +370 DATA 8B,02 +380 ' TFR CC,B + STD $4504 +390 DATA 1F,A9,FD,45,04 +400 ' LDA $4500 +410 DATA B6,45,00 +420 ' ADDA 3 +430 DATA 8B,03 +440 ' TFR CC,B + STD $4506 +450 DATA 1F,A9,FD,45,06 +460 ' LDA $4500 +470 DATA B6,45,00 +480 ' ADDA 4 +490 DATA 8B,04 +500 ' TFR CC,B + STD $4508 +510 DATA 1F,A9,FD,45,08 +520 ' LDA $4500 +530 DATA B6,45,00 +540 ' ADDA 5 +550 DATA 8B,05 +560 ' TFR CC,B + STD $450A +570 DATA 1F,A9,FD,45,0A +580 ' LDA $4500 +590 DATA B6,45,00 +600 ' ADDA 6 +610 DATA 8B,06 +620 ' TFR CC,B + STD $450C +630 DATA 1F,A9,FD,45,0C +640 ' LDA $4500 +650 DATA B6,45,00 +660 ' ADDA 7 +670 DATA 8B,07 +680 ' TFR CC,B + STD $450E +690 DATA 1F,A9,FD,45,0E +700 ' LDA $4500 +710 DATA B6,45,00 +720 ' ADDA 8 +730 DATA 8B,08 +740 ' TFR CC,B + STD $4510 +750 DATA 1F,A9,FD,45,10 +760 ' RTS +770 DATA 39 +780 DATA END diff --git a/TestCC_Registers/testCC_SUBA.bas b/TestCC_Registers/testCC_SUBA.bas new file mode 100644 index 00000000..7c3f9db6 --- /dev/null +++ b/TestCC_Registers/testCC_SUBA.bas @@ -0,0 +1,78 @@ +10 PRINT:PRINT "TEST CC SUBA V0.1" +20 PRINT "(GPL V3 OR ABOVE)" +30 PRINT:PRINT "COPYLEFT (C) 2013 JENS DIEMER":PRINT +40 LA=&H4000 ' LOAD / EXECUTE ADDRESS +50 PRINT "POKE MACHINE CODE TO: $";HEX$(LA) +60 PA = LA ' START ADDRESS FOR POKE +70 READ HB$ ' HEX CONSTANTS +80 IF HB$="END" THEN 140 +90 V=VAL("&H"+HB$) +100 POKE PA,V ' POKE VALUE INTO MEMORY +110 'PRINT "POKE $";HEX$(V);" AT $";HEX$(PA) +120 PA = PA + 1 ' INCREMENT POKE ADDRESS +130 GOTO 70 +140 PRINT "LOADED, END ADDRESS IS: $"; HEX$(PA-1) +150 INIT=3:PRINT +160 PRINT "INIT ACCU A WITH";INIT +170 POKE &H4500,INIT ' SET START VALUE +180 PRINT "START MACHINE CODE PROGRAMM" +190 EXEC LA +200 PRINT "RESULTS:" +210 EA=&H4500 +220 FOR I = 1 TO 8 +230 PRINT I;INIT;"A:";PEEK(EA);" CC:";HEX$(PEEK(EA+1)) +235 INIT=INIT-1 +240 EA=EA+2 +250 NEXT I +270 ' MACHINE CODE IN HEX +280 ' LDA $4500 +290 DATA B6,45,00 +300 ' SUBA 1 +310 DATA 80,01 +320 ' TFR CC,B + STD $4502 +330 DATA 1F,A9,FD,45,02 +340 ' LDA $4500 +350 DATA B6,45,00 +360 ' SUBA 2 +370 DATA 80,02 +380 ' TFR CC,B + STD $4504 +390 DATA 1F,A9,FD,45,04 +400 ' LDA $4500 +410 DATA B6,45,00 +420 ' SUBA 3 +430 DATA 80,03 +440 ' TFR CC,B + STD $4506 +450 DATA 1F,A9,FD,45,06 +460 ' LDA $4500 +470 DATA B6,45,00 +480 ' SUBA 4 +490 DATA 80,04 +500 ' TFR CC,B + STD $4508 +510 DATA 1F,A9,FD,45,08 +520 ' LDA $4500 +530 DATA B6,45,00 +540 ' SUBA 5 +550 DATA 80,05 +560 ' TFR CC,B + STD $450A +570 DATA 1F,A9,FD,45,0A +580 ' LDA $4500 +590 DATA B6,45,00 +600 ' SUBA 6 +610 DATA 80,06 +620 ' TFR CC,B + STD $450C +630 DATA 1F,A9,FD,45,0C +640 ' LDA $4500 +650 DATA B6,45,00 +660 ' SUBA 7 +670 DATA 80,07 +680 ' TFR CC,B + STD $450E +690 DATA 1F,A9,FD,45,0E +700 ' LDA $4500 +710 DATA B6,45,00 +720 ' SUBA 8 +730 DATA 80,08 +740 ' TFR CC,B + STD $4510 +750 DATA 1F,A9,FD,45,10 +760 ' RTS +770 DATA 39 +780 DATA END From 87a549870b1542b212ee88c48632abf2a8cddcb0 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Wed, 16 Oct 2013 12:41:40 +0200 Subject: [PATCH 091/151] Skip empty lines (e.g. XRoar need a empty line at the end) --- PyDC/PyDC/CassetteObjects.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/PyDC/PyDC/CassetteObjects.py b/PyDC/PyDC/CassetteObjects.py index d75762c9..749623bc 100644 --- a/PyDC/PyDC/CassetteObjects.py +++ b/PyDC/PyDC/CassetteObjects.py @@ -56,7 +56,18 @@ def __init__(self, cfg): def create_from_bas(self, file_content): for line in file_content.splitlines(): - line_number, code = line.split(" ", 1) + if not line: + # Skip empty lines (e.g. XRoar need a empty line at the end) + continue + + try: + line_number, code = line.split(" ", 1) + except ValueError: + etype, evalue, etb = sys.exc_info() + evalue = etype( + "Error split line: %s (line: %s)" % (evalue, repr(line)) + ) + raise etype, evalue, etb line_number = int(line_number) if self.cfg.case_convert: From 36d0ba1e4384b3d9ec0b7f843753da08ac52182c Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Mon, 21 Oct 2013 22:33:17 +0200 Subject: [PATCH 092/151] add a CC test with INC --- TestCC_Registers/testCC_INC.bas | 56 +++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 TestCC_Registers/testCC_INC.bas diff --git a/TestCC_Registers/testCC_INC.bas b/TestCC_Registers/testCC_INC.bas new file mode 100644 index 00000000..f3e12d5e --- /dev/null +++ b/TestCC_Registers/testCC_INC.bas @@ -0,0 +1,56 @@ +1 PRINT:PRINT "TEST CC WITH INC V0.1" +2 PRINT "(GPL V3 OR ABOVE)" +3 PRINT:PRINT "COPYLEFT (C) 2013 JENS DIEMER":PRINT +11 COUNT=14 +20 LA=&H4000 ' LOAD / EXECUTE ADDRESS +25 PRINT "POKE MACHINE CODE TO: $";HEX$(LA) +30 PA = LA ' START ADDRESS FOR POKE +50 READ HB$ ' HEX CONSTANTS +60 IF HB$="END" THEN 100 +65 V=VAL("&H"+HB$) +70 POKE PA,V ' POKE VALUE INTO MEMORY +75 'PRINT "POKE $";HEX$(V);" AT $";HEX$(PA) +80 PA = PA + 1 ' INCREMENT POKE ADDRESS +90 GOTO 50 +100 PRINT "LOADED, END ADDRESS IS: $"; HEX$(PA-1) +110 PRINT:INPUT "INPUT START VALUE (DEZ)";A$ +115 IF A$="" THEN 20000 ELSE A=VAL(A$) +120 A=A-1 +130 GOTO 500 +140 PRINT "UP/DOWN OR ANYKEY FOR NEW VALUE"; +150 I$ = INKEY$:IF I$="" THEN 150 +160 IF I$=CHR$(&H5E) THEN A=A-(COUNT*2):GOTO 500 ' UP KEYPRESS +170 IF I$=CHR$(&H0A) THEN 500 ' DOWN KEYPRESS +180 GOTO 110 ' NOT UP/DOWN +500 A=A AND &HFF ' WRAP AROUND +510 POKE &H4500,A ' SET START VALUE +520 'PRINT "A=";A;" VALUE FROM $4500: ";PEEK(&H4500) +540 CLS:PRINT " EFHINZVC" +550 FOR I = 1 TO COUNT +560 EXEC LA +570 CC=PEEK(&H4501) ' CC-REGISTER +580 A=PEEK(&H4500) ' ACCU A +590 ' CREATE BITS +600 T = CC +610 B7$="0":IF T AND 128 THEN B7$="1" +620 B6$="0":IF T AND 64 THEN B6$="1" +630 B5$="0":IF T AND 32 THEN B5$="1" +640 B4$="0":IF T AND 16 THEN B4$="1" +650 B3$="0":IF T AND 8 THEN B3$="1" +660 B2$="0":IF T AND 4 THEN B2$="1" +670 B1$="0":IF T AND 2 THEN B1$="1" +680 B0$="0":IF T AND 1 THEN B1$="1" +690 PRINT "A=";RIGHT$(" "+STR$(A),4);" CC=$";HEX$(CC);":";B7$;B6$;B5$;B4$;B3$;B2$;B1$;B0$ +700 NEXT I +710 GOTO 140 +1000 ' MACHINE CODE IN HEX +1009 ' INC $4500 +1010 DATA 7C,45,00 +1029 ' TFR CC,B +1030 DATA 1F,A9 +1039 ' STB $4501 ; STORE B +1040 DATA F7,45,01 +1049 ' RTS +1050 DATA 39 +10000 DATA END +20000 PRINT:PRINT "BYE" From 34d68be0e08b7510553615e62e05efc855576a83 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Mon, 21 Oct 2013 23:09:47 +0200 Subject: [PATCH 093/151] clear CC before INC --- TestCC_Registers/testCC_INC.bas | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/TestCC_Registers/testCC_INC.bas b/TestCC_Registers/testCC_INC.bas index f3e12d5e..38bf847f 100644 --- a/TestCC_Registers/testCC_INC.bas +++ b/TestCC_Registers/testCC_INC.bas @@ -44,13 +44,20 @@ 700 NEXT I 710 GOTO 140 1000 ' MACHINE CODE IN HEX -1009 ' INC $4500 -1010 DATA 7C,45,00 -1029 ' TFR CC,B -1030 DATA 1F,A9 -1039 ' STB $4501 ; STORE B -1040 DATA F7,45,01 -1049 ' RTS -1050 DATA 39 -10000 DATA END +1005 ' CLR,LDB,ADDB TO CLEAR CC +1010 ' CLR B +1020 DATA 5F +1030 ' LDB $11 ; B=$11 +1040 DATA C6,11 +1050 ' ADDB $1 ; B=B+$1 +1060 DATA CB,01 +1070 ' INC $4500 +1080 DATA 7C,45,00 +1090 ' TFR CC,B +1100 DATA 1F,A9 +1110 ' STB $4501 ; STORE B +1120 DATA F7,45,01 +10000' RTS +10010 DATA 39 +10020 DATA END 20000 PRINT:PRINT "BYE" From dd082a4bab4502d49f2f7ab07d91107154d2b92d Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Tue, 22 Oct 2013 11:01:59 +0200 Subject: [PATCH 094/151] nicer output + add screenshot --- TestCC_Registers/README.creole | 3 +++ TestCC_Registers/testCC_ADDA.bas | 17 ++++++++++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) mode change 100644 => 100755 TestCC_Registers/testCC_ADDA.bas diff --git a/TestCC_Registers/README.creole b/TestCC_Registers/README.creole index 183b76fd..a3f1d5ae 100755 --- a/TestCC_Registers/README.creole +++ b/TestCC_Registers/README.creole @@ -11,6 +11,9 @@ license: GNU GPL v3 or above, see LICENSE for more details. {{http://www.jensdiemer.de/media/jensdiemer.de/screenshots/test_cc_register02.PNG|test_cc_register02.PNG}} +testCC_ADDA.bas: +{{https://www.jensdiemer.de/media/jensdiemer.de/screenshots/testCC_ADDA_01.png|testCC_ADDA_01.png}} + === test diff --git a/TestCC_Registers/testCC_ADDA.bas b/TestCC_Registers/testCC_ADDA.bas old mode 100644 new mode 100755 index ba900d8c..0083dd51 --- a/TestCC_Registers/testCC_ADDA.bas +++ b/TestCC_Registers/testCC_ADDA.bas @@ -1,4 +1,4 @@ -10 PRINT:PRINT "TEST CC ADDA V0.1" +10 CLS:PRINT "TEST CC ADDA V0.2" 20 PRINT "(GPL V3 OR ABOVE)" 30 PRINT:PRINT "COPYLEFT (C) 2013 JENS DIEMER":PRINT 40 LA=&H4000 ' LOAD / EXECUTE ADDRESS @@ -17,10 +17,21 @@ 170 POKE &H4500,INIT ' SET START VALUE 180 PRINT "START MACHINE CODE PROGRAMM" 190 EXEC LA -200 PRINT "RESULTS:" +200 PRINT "RESULTS: EFHINZVC" 210 EA=&H4500 220 FOR I = 1 TO 8 -230 PRINT I;INIT;"A:";PEEK(EA);" CC:";HEX$(PEEK(EA+1)) +221 A=PEEK(EA):CC=PEEK(EA+1) +222 ' CREATE BITS +223 T = CC +224 B7$=".":IF T AND 128 THEN B7$="E" +225 B6$=".":IF T AND 64 THEN B6$="F" +226 B5$=".":IF T AND 32 THEN B5$="H" +227 B4$=".":IF T AND 16 THEN B4$="I" +228 B3$=".":IF T AND 8 THEN B3$="N" +229 B2$=".":IF T AND 4 THEN B2$="Z" +230 B1$=".":IF T AND 2 THEN B1$="V" +231 B0$=".":IF T AND 1 THEN B1$="C" +232 PRINT I;INIT;"A:";RIGHT$(" "+STR$(A),3);" CC=$";RIGHT$(" "+HEX$(CC),2);":";B7$;B6$;B5$;B4$;B3$;B2$;B1$;B0$ 235 INIT=INIT+1 240 EA=EA+2 250 NEXT I From d10db05a89b25c8c9257f8a53d335a43e38bb740 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Tue, 22 Oct 2013 14:52:32 +0200 Subject: [PATCH 095/151] add gaps to ASCII files --- PyDC/PyDC/CassetteObjects.py | 71 ++++++++++++++++++++++-------------- 1 file changed, 44 insertions(+), 27 deletions(-) diff --git a/PyDC/PyDC/CassetteObjects.py b/PyDC/PyDC/CassetteObjects.py index 749623bc..93be509e 100644 --- a/PyDC/PyDC/CassetteObjects.py +++ b/PyDC/PyDC/CassetteObjects.py @@ -22,6 +22,7 @@ LOG_FORMATTER, pformat_codepoints from wave2bitstream import Wave2Bitstream, Bitstream2Wave from bitstream_handler import BitstreamHandler, CasStream, BytestreamHandler +from PyDC.utils import iter_steps log = logging.getLogger("PyDC") @@ -273,6 +274,10 @@ def get_as_codepoints(self): result.append(delim) result += list(code_line.get_as_codepoints()) result.append(delim) +# log.debug("-"*79) +# for line in pformat_codepoints(result): +# log.debug(repr(line)) +# log.debug("-"*79) return result def get_ascii_codeline(self): @@ -294,6 +299,8 @@ class CassetteFile(object): def __init__(self, cfg): self.cfg = cfg self.is_tokenized = False + self.ascii_flag = None + self.gap_flag = None # one byte gap flag (0x00=no gaps, 0xFF=gaps) def create_from_bas(self, filename, file_content): filename2 = os.path.split(filename)[1] @@ -306,7 +313,13 @@ def create_from_bas(self, filename, file_content): log.debug("filename '%s' from: %s" % (filename2, filename)) self.filename = filename2 + self.file_type = self.cfg.FTYPE_BASIC # BASIC programm (0x00) + + # http://archive.worldofdragon.org/phpBB3/viewtopic.php?f=8&t=4231&p=9723#p9723 + self.ascii_flag = self.cfg.BASIC_ASCII + self.gap_flag = self.cfg.GAPS # ASCII File is GAP, tokenized is no gaps + self.file_content = FileContent(self.cfg) self.file_content.create_from_bas(file_content) @@ -384,14 +397,7 @@ def get_filename_block_as_codepoints(self): # one byte gap flag (0x00=no gaps, 0xFF=gaps) # http://archive.worldofdragon.org/phpBB3/viewtopic.php?f=8&t=4231&p=9110#p9110 - codepoints.append(self.cfg.GAPS) - -# if self.ascii_flag == self.cfg.BASIC_ASCII: -# codepoints.append(self.cfg.GAPS) -# elif self.ascii_flag == self.cfg.BASIC_TOKENIZED: -# codepoints.append(self.cfg.NO_GAPS) -# else: -# raise NotImplementedError("Unknown BASIC type: '%s'" % hex(self.ascii_flag)) + codepoints.append(self.gap_flag) # machine code starting/loading address if self.file_type != self.cfg.FTYPE_BASIC: # BASIC programm (0x00) @@ -535,12 +541,18 @@ def print_debug_info(self): for file_obj in self.files: file_obj.print_debug_info() - def block2codepoint_stream(self, block_type, block_codepoints): - log.debug("yield %sx lead byte %s" % ( - self.cfg.LEAD_BYTE_LEN, hex(self.cfg.LEAD_BYTE_CODEPOINT) - )) - leadin = [self.cfg.LEAD_BYTE_CODEPOINT for _ in xrange(self.cfg.LEAD_BYTE_LEN)] - yield leadin + def block2codepoint_stream(self, file_obj, block_type, block_codepoints): + if file_obj.gap_flag == self.cfg.GAPS: + # file has gaps (e.g. ASCII BASIC) + log.debug("File has GAP flag set:") + log.debug("yield %sx bit-sync bytes %s", + self.cfg.LEAD_BYTE_LEN, hex(self.cfg.LEAD_BYTE_CODEPOINT) + ) + leadin = [self.cfg.LEAD_BYTE_CODEPOINT for _ in xrange(self.cfg.LEAD_BYTE_LEN)] + yield leadin + + log.debug("yield 1x leader byte %s", hex(self.cfg.LEAD_BYTE_CODEPOINT)) + yield self.cfg.LEAD_BYTE_CODEPOINT log.debug("yield sync byte %s" % hex(self.cfg.SYNC_BYTE_CODEPOINT)) if self.wav: @@ -550,11 +562,13 @@ def block2codepoint_stream(self, block_type, block_codepoints): log.debug("yield block type '%s'" % self.cfg.BLOCK_TYPE_DICT[block_type]) yield block_type - block_length = len(block_codepoints) + codepoints = tuple(block_codepoints) + block_length = len(codepoints) + assert block_length <= 255 log.debug("yield block length %s (%sBytes)" % (hex(block_length), block_length)) yield block_length - if not block_codepoints: + if not codepoints: # EOF block # FIXME checksum checksum = block_type @@ -565,10 +579,9 @@ def block2codepoint_stream(self, block_type, block_codepoints): else: log.debug("content of '%s':" % self.cfg.BLOCK_TYPE_DICT[block_type]) log.debug("-"*79) - log.debug(pformat_codepoints(block_codepoints)) + log.debug(repr("".join([chr(i) for i in codepoints]))) log.debug("-"*79) - codepoints = tuple(block_codepoints) yield codepoints checksum = sum([codepoint for codepoint in codepoints]) @@ -578,13 +591,16 @@ def block2codepoint_stream(self, block_type, block_codepoints): log.debug("yield calculated checksum %s" % hex(checksum)) yield checksum + log.debug("yield 1x tailer byte %s", hex(self.cfg.LEAD_BYTE_CODEPOINT)) + yield self.cfg.LEAD_BYTE_CODEPOINT + def codepoint_stream(self): if self.wav: self.wav.write_silence(sec=0.1) for file_obj in self.files: # yield filename - for codepoints in self.block2codepoint_stream( + for codepoints in self.block2codepoint_stream(file_obj, block_type=self.cfg.FILENAME_BLOCK, block_codepoints=file_obj.get_filename_block_as_codepoints() ): @@ -594,14 +610,15 @@ def codepoint_stream(self): self.wav.write_silence(sec=0.1) # yield file content - codepoints = tuple(file_obj.get_code_block_as_codepoints()) - codepoints = iter(codepoints) - while True: - raw_codepoints = tuple(itertools.islice(codepoints, 0, 255)) - if not raw_codepoints: - break + codepoints = file_obj.get_code_block_as_codepoints() + + for raw_codepoints in iter_steps(codepoints, 255): +# log.debug("-"*79) +# log.debug("".join([chr(i) for i in raw_codepoints])) +# log.debug("-"*79) + # Add meta information - codepoint_stream = self.block2codepoint_stream( + codepoint_stream = self.block2codepoint_stream(file_obj, block_type=self.cfg.DATA_BLOCK, block_codepoints=raw_codepoints ) for codepoints2 in codepoint_stream: @@ -611,7 +628,7 @@ def codepoint_stream(self): self.wav.write_silence(sec=0.1) # yield EOF - for codepoints in self.block2codepoint_stream( + for codepoints in self.block2codepoint_stream(file_obj, block_type=self.cfg.EOF_BLOCK, block_codepoints=[] ): From ed2e0f7577b0bfaee5eb5b6d1b48626a72b3c617 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Thu, 24 Oct 2013 10:58:15 +0200 Subject: [PATCH 096/151] Update CC test with ADDA --- TestCC_Registers/testCC_ADDA.bas | 155 +++++++++++++------------------ 1 file changed, 66 insertions(+), 89 deletions(-) diff --git a/TestCC_Registers/testCC_ADDA.bas b/TestCC_Registers/testCC_ADDA.bas index 0083dd51..9bc4a8ad 100755 --- a/TestCC_Registers/testCC_ADDA.bas +++ b/TestCC_Registers/testCC_ADDA.bas @@ -1,89 +1,66 @@ -10 CLS:PRINT "TEST CC ADDA V0.2" -20 PRINT "(GPL V3 OR ABOVE)" -30 PRINT:PRINT "COPYLEFT (C) 2013 JENS DIEMER":PRINT -40 LA=&H4000 ' LOAD / EXECUTE ADDRESS -50 PRINT "POKE MACHINE CODE TO: $";HEX$(LA) -60 PA = LA ' START ADDRESS FOR POKE -70 READ HB$ ' HEX CONSTANTS -80 IF HB$="END" THEN 140 -90 V=VAL("&H"+HB$) -100 POKE PA,V ' POKE VALUE INTO MEMORY -110 'PRINT "POKE $";HEX$(V);" AT $";HEX$(PA) -120 PA = PA + 1 ' INCREMENT POKE ADDRESS -130 GOTO 70 -140 PRINT "LOADED, END ADDRESS IS: $"; HEX$(PA-1) -150 INIT=252:PRINT -160 PRINT "INIT ACCU A WITH";INIT -170 POKE &H4500,INIT ' SET START VALUE -180 PRINT "START MACHINE CODE PROGRAMM" -190 EXEC LA -200 PRINT "RESULTS: EFHINZVC" -210 EA=&H4500 -220 FOR I = 1 TO 8 -221 A=PEEK(EA):CC=PEEK(EA+1) -222 ' CREATE BITS -223 T = CC -224 B7$=".":IF T AND 128 THEN B7$="E" -225 B6$=".":IF T AND 64 THEN B6$="F" -226 B5$=".":IF T AND 32 THEN B5$="H" -227 B4$=".":IF T AND 16 THEN B4$="I" -228 B3$=".":IF T AND 8 THEN B3$="N" -229 B2$=".":IF T AND 4 THEN B2$="Z" -230 B1$=".":IF T AND 2 THEN B1$="V" -231 B0$=".":IF T AND 1 THEN B1$="C" -232 PRINT I;INIT;"A:";RIGHT$(" "+STR$(A),3);" CC=$";RIGHT$(" "+HEX$(CC),2);":";B7$;B6$;B5$;B4$;B3$;B2$;B1$;B0$ -235 INIT=INIT+1 -240 EA=EA+2 -250 NEXT I -270 ' MACHINE CODE IN HEX -280 ' LDA $4500 -290 DATA B6,45,00 -300 ' ADDA 1 -310 DATA 8B,01 -320 ' TFR CC,B + STD $4502 -330 DATA 1F,A9,FD,45,02 -340 ' LDA $4500 -350 DATA B6,45,00 -360 ' ADDA 2 -370 DATA 8B,02 -380 ' TFR CC,B + STD $4504 -390 DATA 1F,A9,FD,45,04 -400 ' LDA $4500 -410 DATA B6,45,00 -420 ' ADDA 3 -430 DATA 8B,03 -440 ' TFR CC,B + STD $4506 -450 DATA 1F,A9,FD,45,06 -460 ' LDA $4500 -470 DATA B6,45,00 -480 ' ADDA 4 -490 DATA 8B,04 -500 ' TFR CC,B + STD $4508 -510 DATA 1F,A9,FD,45,08 -520 ' LDA $4500 -530 DATA B6,45,00 -540 ' ADDA 5 -550 DATA 8B,05 -560 ' TFR CC,B + STD $450A -570 DATA 1F,A9,FD,45,0A -580 ' LDA $4500 -590 DATA B6,45,00 -600 ' ADDA 6 -610 DATA 8B,06 -620 ' TFR CC,B + STD $450C -630 DATA 1F,A9,FD,45,0C -640 ' LDA $4500 -650 DATA B6,45,00 -660 ' ADDA 7 -670 DATA 8B,07 -680 ' TFR CC,B + STD $450E -690 DATA 1F,A9,FD,45,0E -700 ' LDA $4500 -710 DATA B6,45,00 -720 ' ADDA 8 -730 DATA 8B,08 -740 ' TFR CC,B + STD $4510 -750 DATA 1F,A9,FD,45,10 -760 ' RTS -770 DATA 39 -780 DATA END +1 PRINT:PRINT "TEST CC WITH ADDA V0.2" +2 PRINT "(GPL V3 OR ABOVE)" +3 PRINT:PRINT "COPYLEFT (C) 2013 JENS DIEMER":PRINT +11 COUNT=14 +20 LA=&H4000 ' LOAD / EXECUTE ADDRESS +25 PRINT "POKE MACHINE CODE TO: $";HEX$(LA) +30 PA = LA ' START ADDRESS FOR POKE +50 READ HB$ ' HEX CONSTANTS +60 IF HB$="END" THEN 100 +65 V=VAL("&H"+HB$) +70 POKE PA,V ' POKE VALUE INTO MEMORY +75 'PRINT "POKE $";HEX$(V);" AT $";HEX$(PA) +80 PA = PA + 1 ' INCREMENT POKE ADDRESS +90 GOTO 50 +100 PRINT "LOADED, END ADDRESS IS: $"; HEX$(PA-1) +110 PRINT:INPUT "INPUT START VALUE (DEZ)";A$ +115 IF A$="" THEN 20000 ELSE A=VAL(A$) +120 A=A-1 +130 GOTO 500 +140 PRINT "UP/DOWN OR ANYKEY FOR NEW VALUE"; +150 I$ = INKEY$:IF I$="" THEN 150 +160 IF I$=CHR$(&H5E) THEN A=A2-(COUNT*2) : GOTO 500 ' UP KEYPRESS +170 IF I$=CHR$(&H0A) THEN A=A2 : GOTO 500 ' DOWN KEYPRESS +180 GOTO 110 ' NOT UP/DOWN +500 CLS:PRINT "A=";A;" VALUE FROM $4500: ";PEEK(&H4500) +540 PRINT " EFHINZVC" +550 FOR I = 1 TO COUNT +551 A2=(A+I) AND &HFF +552 'PRINT "SET A=";A2 +553 POKE &H4500,A2 ' SET START VALUE +560 EXEC LA +570 CC=PEEK(&H4501) ' CC-REGISTER +580 A3=PEEK(&H4500) ' ACCU A +590 ' CREATE BITS +600 T = CC +610 B7$=".":IF T AND 128 THEN B7$="E" +620 B6$=".":IF T AND 64 THEN B6$="F" +630 B5$=".":IF T AND 32 THEN B5$="H" +640 B4$=".":IF T AND 16 THEN B4$="I" +650 B3$=".":IF T AND 8 THEN B3$="N" +660 B2$=".":IF T AND 4 THEN B2$="Z" +670 B1$=".":IF T AND 2 THEN B1$="V" +680 B0$=".":IF T AND 1 THEN B0$="C" +690 PRINT "A=";RIGHT$(" "+STR$(A3),4);" CC=$";RIGHT$(" "+HEX$(CC),2);":";B7$;B6$;B5$;B4$;B3$;B2$;B1$;B0$ +700 NEXT I +710 GOTO 140 +1000 ' MACHINE CODE IN HEX +1010 ' LDA $4500 +1020 DATA B6,45,00 +1030 ' CLR,TFR TO CLEAR CC +1040 ' CLR B +1050 DATA 5F +1060 ' TFR B,CC +1070 DATA 1F,9A +1080 ' ADDA #1 +1090 DATA 8B,01 +2000 ' TFR CC,B +2010 DATA 1F,A9 +2020 ' STA $4500 +2030 DATA B7,45,00 +2040 ' STB $4501 +2050 DATA F7,45,01 +10000 ' RTS +10010 DATA 39 +10020 DATA END +20000 PRINT:PRINT "BYE" From b61b564cdf2a6ac579f4bdc54ecb760dd718b33a Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Thu, 24 Oct 2013 15:40:24 +0200 Subject: [PATCH 097/151] update CC test with SUBA --- TestCC_Registers/testCC_SUBA.bas | 144 ++++++++++++++----------------- 1 file changed, 66 insertions(+), 78 deletions(-) mode change 100644 => 100755 TestCC_Registers/testCC_SUBA.bas diff --git a/TestCC_Registers/testCC_SUBA.bas b/TestCC_Registers/testCC_SUBA.bas old mode 100644 new mode 100755 index 7c3f9db6..a9f11c51 --- a/TestCC_Registers/testCC_SUBA.bas +++ b/TestCC_Registers/testCC_SUBA.bas @@ -1,78 +1,66 @@ -10 PRINT:PRINT "TEST CC SUBA V0.1" -20 PRINT "(GPL V3 OR ABOVE)" -30 PRINT:PRINT "COPYLEFT (C) 2013 JENS DIEMER":PRINT -40 LA=&H4000 ' LOAD / EXECUTE ADDRESS -50 PRINT "POKE MACHINE CODE TO: $";HEX$(LA) -60 PA = LA ' START ADDRESS FOR POKE -70 READ HB$ ' HEX CONSTANTS -80 IF HB$="END" THEN 140 -90 V=VAL("&H"+HB$) -100 POKE PA,V ' POKE VALUE INTO MEMORY -110 'PRINT "POKE $";HEX$(V);" AT $";HEX$(PA) -120 PA = PA + 1 ' INCREMENT POKE ADDRESS -130 GOTO 70 -140 PRINT "LOADED, END ADDRESS IS: $"; HEX$(PA-1) -150 INIT=3:PRINT -160 PRINT "INIT ACCU A WITH";INIT -170 POKE &H4500,INIT ' SET START VALUE -180 PRINT "START MACHINE CODE PROGRAMM" -190 EXEC LA -200 PRINT "RESULTS:" -210 EA=&H4500 -220 FOR I = 1 TO 8 -230 PRINT I;INIT;"A:";PEEK(EA);" CC:";HEX$(PEEK(EA+1)) -235 INIT=INIT-1 -240 EA=EA+2 -250 NEXT I -270 ' MACHINE CODE IN HEX -280 ' LDA $4500 -290 DATA B6,45,00 -300 ' SUBA 1 -310 DATA 80,01 -320 ' TFR CC,B + STD $4502 -330 DATA 1F,A9,FD,45,02 -340 ' LDA $4500 -350 DATA B6,45,00 -360 ' SUBA 2 -370 DATA 80,02 -380 ' TFR CC,B + STD $4504 -390 DATA 1F,A9,FD,45,04 -400 ' LDA $4500 -410 DATA B6,45,00 -420 ' SUBA 3 -430 DATA 80,03 -440 ' TFR CC,B + STD $4506 -450 DATA 1F,A9,FD,45,06 -460 ' LDA $4500 -470 DATA B6,45,00 -480 ' SUBA 4 -490 DATA 80,04 -500 ' TFR CC,B + STD $4508 -510 DATA 1F,A9,FD,45,08 -520 ' LDA $4500 -530 DATA B6,45,00 -540 ' SUBA 5 -550 DATA 80,05 -560 ' TFR CC,B + STD $450A -570 DATA 1F,A9,FD,45,0A -580 ' LDA $4500 -590 DATA B6,45,00 -600 ' SUBA 6 -610 DATA 80,06 -620 ' TFR CC,B + STD $450C -630 DATA 1F,A9,FD,45,0C -640 ' LDA $4500 -650 DATA B6,45,00 -660 ' SUBA 7 -670 DATA 80,07 -680 ' TFR CC,B + STD $450E -690 DATA 1F,A9,FD,45,0E -700 ' LDA $4500 -710 DATA B6,45,00 -720 ' SUBA 8 -730 DATA 80,08 -740 ' TFR CC,B + STD $4510 -750 DATA 1F,A9,FD,45,10 -760 ' RTS -770 DATA 39 -780 DATA END +1 PRINT:PRINT "TEST CC WITH SUBA V0.2" +2 PRINT "(GPL V3 OR ABOVE)" +3 PRINT:PRINT "COPYLEFT (C) 2013 JENS DIEMER":PRINT +11 COUNT=14 +20 LA=&H4000 ' LOAD / EXECUTE ADDRESS +25 PRINT "POKE MACHINE CODE TO: $";HEX$(LA) +30 PA = LA ' START ADDRESS FOR POKE +50 READ HB$ ' HEX CONSTANTS +60 IF HB$="END" THEN 100 +65 V=VAL("&H"+HB$) +70 POKE PA,V ' POKE VALUE INTO MEMORY +75 'PRINT "POKE $";HEX$(V);" AT $";HEX$(PA) +80 PA = PA + 1 ' INCREMENT POKE ADDRESS +90 GOTO 50 +100 PRINT "LOADED, END ADDRESS IS: $"; HEX$(PA-1) +110 PRINT:INPUT "INPUT START VALUE (DEZ)";A$ +115 IF A$="" THEN 20000 ELSE A=VAL(A$) +120 A=A-1 +130 GOTO 500 +140 PRINT "UP/DOWN OR ANYKEY FOR NEW VALUE"; +150 I$ = INKEY$:IF I$="" THEN 150 +160 IF I$=CHR$(&H5E) THEN A=A2+(COUNT*2) : GOTO 500 ' UP KEYPRESS +170 IF I$=CHR$(&H0A) THEN A=A2 : GOTO 500 ' DOWN KEYPRESS +180 GOTO 110 ' NOT UP/DOWN +500 CLS:PRINT "A=";A;" VALUE FROM $4500: ";PEEK(&H4500) +540 PRINT " EFHINZVC" +550 FOR I = 1 TO COUNT +551 A2=(A-I) AND &HFF +552 'PRINT "SET A=";A2 +553 POKE &H4500,A2 ' SET START VALUE +560 EXEC LA +570 CC=PEEK(&H4501) ' CC-REGISTER +580 A3=PEEK(&H4500) ' ACCU A +590 ' CREATE BITS +600 T = CC +610 B7$=".":IF T AND 128 THEN B7$="E" +620 B6$=".":IF T AND 64 THEN B6$="F" +630 B5$=".":IF T AND 32 THEN B5$="H" +640 B4$=".":IF T AND 16 THEN B4$="I" +650 B3$=".":IF T AND 8 THEN B3$="N" +660 B2$=".":IF T AND 4 THEN B2$="Z" +670 B1$=".":IF T AND 2 THEN B1$="V" +680 B0$=".":IF T AND 1 THEN B0$="C" +690 PRINT "A=";RIGHT$(" "+STR$(A3),4);" CC=$";RIGHT$(" "+HEX$(CC),2);":";B7$;B6$;B5$;B4$;B3$;B2$;B1$;B0$ +700 NEXT I +710 GOTO 140 +1000 ' MACHINE CODE IN HEX +1010 ' LDA $4500 +1020 DATA B6,45,00 +1030 ' CLR,TFR TO CLEAR CC +1040 ' CLR B +1050 DATA 5F +1060 ' TFR B,CC +1070 DATA 1F,9A +1080 ' SUB #1 +1090 DATA 80,01 +2000 ' TFR CC,B +2010 DATA 1F,A9 +2020 ' STA $4500 +2030 DATA B7,45,00 +2040 ' STB $4501 +2050 DATA F7,45,01 +10000 ' RTS +10010 DATA 39 +10020 DATA END +20000 PRINT:PRINT "BYE" From c83758e8f986f6a83450a61d5276a04f04943e64 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Thu, 24 Oct 2013 17:53:55 +0200 Subject: [PATCH 098/151] Add CC test with DEC --- TestCC_Registers/testCC_DEC.bas | 62 +++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100755 TestCC_Registers/testCC_DEC.bas diff --git a/TestCC_Registers/testCC_DEC.bas b/TestCC_Registers/testCC_DEC.bas new file mode 100755 index 00000000..94cd280d --- /dev/null +++ b/TestCC_Registers/testCC_DEC.bas @@ -0,0 +1,62 @@ +1 PRINT:PRINT "TEST CC WITH DEC V0.2" +2 PRINT "(GPL V3 OR ABOVE)" +3 PRINT:PRINT "COPYLEFT (C) 2013 JENS DIEMER":PRINT +11 COUNT=14 +20 LA=&H4000 ' LOAD / EXECUTE ADDRESS +25 PRINT "POKE MACHINE CODE TO: $";HEX$(LA) +30 PA = LA ' START ADDRESS FOR POKE +50 READ HB$ ' HEX CONSTANTS +60 IF HB$="END" THEN 100 +65 V=VAL("&H"+HB$) +70 POKE PA,V ' POKE VALUE INTO MEMORY +75 'PRINT "POKE $";HEX$(V);" AT $";HEX$(PA) +80 PA = PA + 1 ' INCREMENT POKE ADDRESS +90 GOTO 50 +100 PRINT "LOADED, END ADDRESS IS: $"; HEX$(PA-1) +110 PRINT:INPUT "INPUT START VALUE (DEZ)";A$ +115 IF A$="" THEN 20000 ELSE A=VAL(A$) +120 A=A-1 +130 GOTO 500 +140 PRINT "UP/DOWN OR ANYKEY FOR NEW VALUE"; +150 I$ = INKEY$:IF I$="" THEN 150 +160 IF I$=CHR$(&H5E) THEN A=A2+(COUNT*2) : GOTO 500 ' UP KEYPRESS +170 IF I$=CHR$(&H0A) THEN A=A2 : GOTO 500 ' DOWN KEYPRESS +180 GOTO 110 ' NOT UP/DOWN +500 CLS:PRINT "A=";A;" VALUE FROM $4500: ";PEEK(&H4500) +540 PRINT " EFHINZVC" +550 FOR I = 1 TO COUNT +551 A2=(A-I) AND &HFF +552 'PRINT "SET A=";A2 +553 POKE &H4500,A2 ' SET START VALUE +560 EXEC LA +570 CC=PEEK(&H4501) ' CC-REGISTER +580 A3=PEEK(&H4500) ' DEC RESULT +590 ' CREATE BITS +600 T = CC +610 B7$=".":IF T AND 128 THEN B7$="E" +620 B6$=".":IF T AND 64 THEN B6$="F" +630 B5$=".":IF T AND 32 THEN B5$="H" +640 B4$=".":IF T AND 16 THEN B4$="I" +650 B3$=".":IF T AND 8 THEN B3$="N" +660 B2$=".":IF T AND 4 THEN B2$="Z" +670 B1$=".":IF T AND 2 THEN B1$="V" +680 B0$=".":IF T AND 1 THEN B0$="C" +690 PRINT "A=";RIGHT$(" "+STR$(A3),4);" CC=$";RIGHT$(" "+HEX$(CC),2);":";B7$;B6$;B5$;B4$;B3$;B2$;B1$;B0$ +700 NEXT I +710 GOTO 140 +1000 ' MACHINE CODE IN HEX +1030 ' CLR,TFR TO CLEAR CC +1040 ' CLR B +1050 DATA 5F +1060 ' TFR B,CC +1070 DATA 1F,9A +1080 ' DEC $4500 +1090 DATA 7A,45,00 +2000 ' TFR CC,B +2010 DATA 1F,A9 +2040 ' STB $4501 +2050 DATA F7,45,01 +10000 ' RTS +10010 DATA 39 +10020 DATA END +20000 PRINT:PRINT "BYE" From fd55238700680531a94954fd7fff9109a86bdeda Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Thu, 24 Oct 2013 17:54:07 +0200 Subject: [PATCH 099/151] update CC test with INC --- TestCC_Registers/testCC_INC.bas | 61 ++++++++++++++++----------------- 1 file changed, 30 insertions(+), 31 deletions(-) mode change 100644 => 100755 TestCC_Registers/testCC_INC.bas diff --git a/TestCC_Registers/testCC_INC.bas b/TestCC_Registers/testCC_INC.bas old mode 100644 new mode 100755 index 38bf847f..52abb84b --- a/TestCC_Registers/testCC_INC.bas +++ b/TestCC_Registers/testCC_INC.bas @@ -1,4 +1,4 @@ -1 PRINT:PRINT "TEST CC WITH INC V0.1" +1 PRINT:PRINT "TEST CC WITH INC V0.2" 2 PRINT "(GPL V3 OR ABOVE)" 3 PRINT:PRINT "COPYLEFT (C) 2013 JENS DIEMER":PRINT 11 COUNT=14 @@ -19,45 +19,44 @@ 130 GOTO 500 140 PRINT "UP/DOWN OR ANYKEY FOR NEW VALUE"; 150 I$ = INKEY$:IF I$="" THEN 150 -160 IF I$=CHR$(&H5E) THEN A=A-(COUNT*2):GOTO 500 ' UP KEYPRESS -170 IF I$=CHR$(&H0A) THEN 500 ' DOWN KEYPRESS +160 IF I$=CHR$(&H5E) THEN A=A2-(COUNT*2) : GOTO 500 ' UP KEYPRESS +170 IF I$=CHR$(&H0A) THEN A=A2 : GOTO 500 ' DOWN KEYPRESS 180 GOTO 110 ' NOT UP/DOWN -500 A=A AND &HFF ' WRAP AROUND -510 POKE &H4500,A ' SET START VALUE -520 'PRINT "A=";A;" VALUE FROM $4500: ";PEEK(&H4500) -540 CLS:PRINT " EFHINZVC" +500 CLS:PRINT "A=";A;" VALUE FROM $4500: ";PEEK(&H4500) +540 PRINT " EFHINZVC" 550 FOR I = 1 TO COUNT +551 A2=(A+I) AND &HFF +552 'PRINT "SET A=";A2 +553 POKE &H4500,A2 ' SET START VALUE 560 EXEC LA 570 CC=PEEK(&H4501) ' CC-REGISTER -580 A=PEEK(&H4500) ' ACCU A +580 A3=PEEK(&H4500) ' INC RESULT 590 ' CREATE BITS 600 T = CC -610 B7$="0":IF T AND 128 THEN B7$="1" -620 B6$="0":IF T AND 64 THEN B6$="1" -630 B5$="0":IF T AND 32 THEN B5$="1" -640 B4$="0":IF T AND 16 THEN B4$="1" -650 B3$="0":IF T AND 8 THEN B3$="1" -660 B2$="0":IF T AND 4 THEN B2$="1" -670 B1$="0":IF T AND 2 THEN B1$="1" -680 B0$="0":IF T AND 1 THEN B1$="1" -690 PRINT "A=";RIGHT$(" "+STR$(A),4);" CC=$";HEX$(CC);":";B7$;B6$;B5$;B4$;B3$;B2$;B1$;B0$ +610 B7$=".":IF T AND 128 THEN B7$="E" +620 B6$=".":IF T AND 64 THEN B6$="F" +630 B5$=".":IF T AND 32 THEN B5$="H" +640 B4$=".":IF T AND 16 THEN B4$="I" +650 B3$=".":IF T AND 8 THEN B3$="N" +660 B2$=".":IF T AND 4 THEN B2$="Z" +670 B1$=".":IF T AND 2 THEN B1$="V" +680 B0$=".":IF T AND 1 THEN B0$="C" +690 PRINT "A=";RIGHT$(" "+STR$(A3),4);" CC=$";RIGHT$(" "+HEX$(CC),2);":";B7$;B6$;B5$;B4$;B3$;B2$;B1$;B0$ 700 NEXT I 710 GOTO 140 1000 ' MACHINE CODE IN HEX -1005 ' CLR,LDB,ADDB TO CLEAR CC -1010 ' CLR B -1020 DATA 5F -1030 ' LDB $11 ; B=$11 -1040 DATA C6,11 -1050 ' ADDB $1 ; B=B+$1 -1060 DATA CB,01 -1070 ' INC $4500 -1080 DATA 7C,45,00 -1090 ' TFR CC,B -1100 DATA 1F,A9 -1110 ' STB $4501 ; STORE B -1120 DATA F7,45,01 -10000' RTS +1030 ' CLR,TFR TO CLEAR CC +1040 ' CLR B +1050 DATA 5F +1060 ' TFR B,CC +1070 DATA 1F,9A +1080 ' INC $4500 +1090 DATA 7C,45,00 +2000 ' TFR CC,B +2010 DATA 1F,A9 +2040 ' STB $4501 +2050 DATA F7,45,01 +10000 ' RTS 10010 DATA 39 10020 DATA END 20000 PRINT:PRINT "BYE" From e00b180b0020d8720e25b5f9579b0a46ec7a488e Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Thu, 24 Oct 2013 18:21:39 +0200 Subject: [PATCH 100/151] Add CC test with TST --- TestCC_Registers/testCC_TST.bas | 62 +++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100755 TestCC_Registers/testCC_TST.bas diff --git a/TestCC_Registers/testCC_TST.bas b/TestCC_Registers/testCC_TST.bas new file mode 100755 index 00000000..f5705153 --- /dev/null +++ b/TestCC_Registers/testCC_TST.bas @@ -0,0 +1,62 @@ +1 PRINT:PRINT "TEST CC WITH TST V0.2" +2 PRINT "(GPL V3 OR ABOVE)" +3 PRINT:PRINT "COPYLEFT (C) 2013 JENS DIEMER":PRINT +11 COUNT=14 +20 LA=&H4000 ' LOAD / EXECUTE ADDRESS +25 PRINT "POKE MACHINE CODE TO: $";HEX$(LA) +30 PA = LA ' START ADDRESS FOR POKE +50 READ HB$ ' HEX CONSTANTS +60 IF HB$="END" THEN 100 +65 V=VAL("&H"+HB$) +70 POKE PA,V ' POKE VALUE INTO MEMORY +75 'PRINT "POKE $";HEX$(V);" AT $";HEX$(PA) +80 PA = PA + 1 ' INCREMENT POKE ADDRESS +90 GOTO 50 +100 PRINT "LOADED, END ADDRESS IS: $"; HEX$(PA-1) +110 PRINT:INPUT "INPUT START VALUE (DEZ)";A$ +115 IF A$="" THEN 20000 ELSE A=VAL(A$) +120 A=A-1 +130 GOTO 500 +140 PRINT "UP/DOWN OR ANYKEY FOR NEW VALUE"; +150 I$ = INKEY$:IF I$="" THEN 150 +160 IF I$=CHR$(&H5E) THEN A=A2-(COUNT*2) : GOTO 500 ' UP KEYPRESS +170 IF I$=CHR$(&H0A) THEN A=A2 : GOTO 500 ' DOWN KEYPRESS +180 GOTO 110 ' NOT UP/DOWN +500 CLS:PRINT "A=";A;" VALUE FROM $4500: ";PEEK(&H4500) +540 PRINT " EFHINZVC" +550 FOR I = 1 TO COUNT +551 A2=(A+I) AND &HFF +552 'PRINT "SET A=";A2 +553 POKE &H4500,A2 ' SET START VALUE +560 EXEC LA +570 CC=PEEK(&H4501) ' CC-REGISTER +580 A3=PEEK(&H4500) ' INC RESULT +590 ' CREATE BITS +600 T = CC +610 B7$=".":IF T AND 128 THEN B7$="E" +620 B6$=".":IF T AND 64 THEN B6$="F" +630 B5$=".":IF T AND 32 THEN B5$="H" +640 B4$=".":IF T AND 16 THEN B4$="I" +650 B3$=".":IF T AND 8 THEN B3$="N" +660 B2$=".":IF T AND 4 THEN B2$="Z" +670 B1$=".":IF T AND 2 THEN B1$="V" +680 B0$=".":IF T AND 1 THEN B0$="C" +690 PRINT "A=";RIGHT$(" "+STR$(A3),4);" CC=$";RIGHT$(" "+HEX$(CC),2);":";B7$;B6$;B5$;B4$;B3$;B2$;B1$;B0$ +700 NEXT I +710 GOTO 140 +1000 ' MACHINE CODE IN HEX +1030 ' CLR,TFR TO CLEAR CC +1040 ' CLR B +1050 DATA 5F +1060 ' TFR B,CC +1070 DATA 1F,9A +1080 ' TST $4500 +1090 DATA 7D,45,00 +2000 ' TFR CC,B +2010 DATA 1F,A9 +2040 ' STB $4501 +2050 DATA F7,45,01 +10000 ' RTS +10010 DATA 39 +10020 DATA END +20000 PRINT:PRINT "BYE" From aeddd304a04cb3449225070ce6bee1f19d2add1c Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Fri, 25 Oct 2013 23:28:11 +0200 Subject: [PATCH 101/151] add CC test with LSL --- TestCC_Registers/testCC_LSL.bas | 66 +++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100755 TestCC_Registers/testCC_LSL.bas diff --git a/TestCC_Registers/testCC_LSL.bas b/TestCC_Registers/testCC_LSL.bas new file mode 100755 index 00000000..357c2317 --- /dev/null +++ b/TestCC_Registers/testCC_LSL.bas @@ -0,0 +1,66 @@ +1 PRINT:PRINT "TEST CC WITH LSL V0.2" +2 PRINT "(GPL V3 OR ABOVE)" +3 PRINT:PRINT "COPYLEFT (C) 2013 JENS DIEMER":PRINT +11 COUNT=14 +20 LA=&H4000 ' LOAD / EXECUTE ADDRESS +25 PRINT "POKE MACHINE CODE TO: $";HEX$(LA) +30 PA = LA ' START ADDRESS FOR POKE +50 READ HB$ ' HEX CONSTANTS +60 IF HB$="END" THEN 100 +65 V=VAL("&H"+HB$) +70 POKE PA,V ' POKE VALUE INTO MEMORY +75 'PRINT "POKE $";HEX$(V);" AT $";HEX$(PA) +80 PA = PA + 1 ' INCREMENT POKE ADDRESS +90 GOTO 50 +100 PRINT "LOADED, END ADDRESS IS: $"; HEX$(PA-1) +110 PRINT:INPUT "INPUT START VALUE (DEZ)";A$ +115 IF A$="" THEN 20000 ELSE A=VAL(A$) +120 A=A-1 +130 GOTO 500 +140 PRINT "UP/DOWN OR ANYKEY FOR NEW VALUE"; +150 I$ = INKEY$:IF I$="" THEN 150 +160 IF I$=CHR$(&H5E) THEN A=A2-(COUNT*2) : GOTO 500 ' UP KEYPRESS +170 IF I$=CHR$(&H0A) THEN A=A2 : GOTO 500 ' DOWN KEYPRESS +180 GOTO 110 ' NOT UP/DOWN +500 CLS:PRINT "A=";A;" VALUE FROM $4500: ";PEEK(&H4500) +540 PRINT " EFHINZVC" +550 FOR I = 1 TO COUNT +551 A2=(A+I) AND &HFF +552 'PRINT "SET A=";A2 +553 POKE &H4500,A2 ' SET START VALUE +560 EXEC LA +570 CC=PEEK(&H4501) ' CC-REGISTER +580 A3=PEEK(&H4500) ' INC RESULT +590 ' CREATE BITS +600 T = CC +610 B7$=".":IF T AND 128 THEN B7$="E" +620 B6$=".":IF T AND 64 THEN B6$="F" +630 B5$=".":IF T AND 32 THEN B5$="H" +640 B4$=".":IF T AND 16 THEN B4$="I" +650 B3$=".":IF T AND 8 THEN B3$="N" +660 B2$=".":IF T AND 4 THEN B2$="Z" +670 B1$=".":IF T AND 2 THEN B1$="V" +680 B0$=".":IF T AND 1 THEN B0$="C" +690 PRINT "A=";RIGHT$(" "+STR$(A3),4);" CC=$";RIGHT$(" "+HEX$(CC),2);":";B7$;B6$;B5$;B4$;B3$;B2$;B1$;B0$ +700 NEXT I +710 GOTO 140 +1000 ' MACHINE CODE IN HEX +1010 ' LDA $4500 +1020 DATA B6,45,00 +1030 ' CLR,TFR TO CLEAR CC +1040 ' CLR B +1050 DATA 5F +1060 ' TFR B,CC +1070 DATA 1F,9A +1080 ' LSLA/ASLA +1090 DATA 48 +2000 ' TFR CC,B +2010 DATA 1F,A9 +2020 ' STA $4500 +2030 DATA B7,45,00 +2040 ' STB $4501 +2050 DATA F7,45,01 +10000 ' RTS +10010 DATA 39 +10020 DATA END +20000 PRINT:PRINT "BYE" From 911e4f57e03e54cda5eec933afd1709fdff6d392 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Fri, 27 Jun 2014 17:41:54 +0200 Subject: [PATCH 102/151] Add NEGA test --- TestCC_Registers/testCC_NEGA.bas | 65 ++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100755 TestCC_Registers/testCC_NEGA.bas diff --git a/TestCC_Registers/testCC_NEGA.bas b/TestCC_Registers/testCC_NEGA.bas new file mode 100755 index 00000000..b711f652 --- /dev/null +++ b/TestCC_Registers/testCC_NEGA.bas @@ -0,0 +1,65 @@ +1 CLS:PRINT "TEST CC WITH NEGA V0.3" +2 PRINT "(GPL V3 OR ABOVE)" +3 PRINT:PRINT "COPYLEFT 2013-2014 JENS DIEMER":PRINT +11 COUNT=15 +20 LA=&H4000 ' LOAD / EXECUTE ADDRESS +25 PRINT "POKE MACHINE CODE TO: $";HEX$(LA) +30 PA = LA ' START ADDRESS FOR POKE +50 READ HB$ ' HEX CONSTANTS +60 IF HB$="END" THEN 100 +65 V=VAL("&H"+HB$) +70 POKE PA,V ' POKE VALUE INTO MEMORY +75 'PRINT "POKE $";HEX$(V);" AT $";HEX$(PA) +80 PA = PA + 1 ' INCREMENT POKE ADDRESS +90 GOTO 50 +100 PRINT "LOADED, END ADDRESS IS: $"; HEX$(PA-1) +110 PRINT:INPUT "INPUT START VALUE (DEZ)";A$ +115 IF A$="" THEN 20000 ELSE A=VAL(A$) +120 A=A-1 +130 GOTO 500 +140 PRINT "UP/DOWN OR ANYKEY FOR NEW VALUE"; +150 I$ = INKEY$:IF I$="" THEN 150 +160 IF I$=CHR$(&H5E) THEN A=A2-(COUNT*2) : GOTO 500 ' UP KEYPRESS +170 IF I$=CHR$(&H0A) THEN A=A2 : GOTO 500 ' DOWN KEYPRESS +180 GOTO 110 ' NOT UP/DOWN +500 CLS +550 FOR I = 1 TO COUNT +551 A2=(A+I) AND &HFF +552 'PRINT "SET A=";A2 +553 POKE &H4500,A2 ' SET IN VALUE +560 EXEC LA ' RUN MACHINE CODE +570 CC=PEEK(&H4501) ' GET CC-REGISTER +580 A3=PEEK(&H4500) ' GET OUT VALUE +590 ' CREATE BITS +600 T = CC +610 B7$="E":IF T AND 128 THEN B7$="e" +620 B6$="F":IF T AND 64 THEN B6$="f" +630 B5$="H":IF T AND 32 THEN B5$="h" +640 B4$="I":IF T AND 16 THEN B4$="i" +650 B3$="N":IF T AND 8 THEN B3$="n" +660 B2$="Z":IF T AND 4 THEN B2$="z" +670 B1$="V":IF T AND 2 THEN B1$="v" +680 B0$="C":IF T AND 1 THEN B0$="c" +690 PRINT LEFT$(STR$(A2)+" ",4);"> neg >";LEFT$(STR$(A3)+" ",4);" cc=$";RIGHT$(" "+HEX$(CC),2);":";B7$;B6$;B5$;B4$;B3$;B2$;B1$;B0$ +700 NEXT I +710 GOTO 140 +1000 ' MACHINE CODE IN HEX +1010 ' LDA $4500 +1020 DATA B6,45,00 +1030 ' CLR,TFR TO CLEAR CC +1040 ' CLRB +1050 DATA 5F +1060 ' TFR B,CC +1070 DATA 1F,9A +1080 ' NEGA +1090 DATA 40 +2000 ' TFR CC,B +2010 DATA 1F,A9 +2020 ' STA $4500 +2030 DATA B7,45,00 +2040 ' STB $4501 +2050 DATA F7,45,01 +10000 ' RTS +10010 DATA 39 +10020 DATA END +20000 PRINT:PRINT "BYE" From 40969b2ef4833d55fbab04ed74a2443c02a33870 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Fri, 27 Jun 2014 17:45:40 +0200 Subject: [PATCH 103/151] update TST --- TestCC_Registers/testCC_TST.bas | 34 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/TestCC_Registers/testCC_TST.bas b/TestCC_Registers/testCC_TST.bas index f5705153..4937e079 100755 --- a/TestCC_Registers/testCC_TST.bas +++ b/TestCC_Registers/testCC_TST.bas @@ -1,7 +1,7 @@ -1 PRINT:PRINT "TEST CC WITH TST V0.2" +1 CLS:PRINT "TEST CC WITH TST V0.3" 2 PRINT "(GPL V3 OR ABOVE)" -3 PRINT:PRINT "COPYLEFT (C) 2013 JENS DIEMER":PRINT -11 COUNT=14 +3 PRINT:PRINT "COPYLEFT 2013-2014 JENS DIEMER":PRINT +11 COUNT=15 20 LA=&H4000 ' LOAD / EXECUTE ADDRESS 25 PRINT "POKE MACHINE CODE TO: $";HEX$(LA) 30 PA = LA ' START ADDRESS FOR POKE @@ -22,26 +22,24 @@ 160 IF I$=CHR$(&H5E) THEN A=A2-(COUNT*2) : GOTO 500 ' UP KEYPRESS 170 IF I$=CHR$(&H0A) THEN A=A2 : GOTO 500 ' DOWN KEYPRESS 180 GOTO 110 ' NOT UP/DOWN -500 CLS:PRINT "A=";A;" VALUE FROM $4500: ";PEEK(&H4500) -540 PRINT " EFHINZVC" +500 CLS 550 FOR I = 1 TO COUNT 551 A2=(A+I) AND &HFF 552 'PRINT "SET A=";A2 -553 POKE &H4500,A2 ' SET START VALUE -560 EXEC LA -570 CC=PEEK(&H4501) ' CC-REGISTER -580 A3=PEEK(&H4500) ' INC RESULT +553 POKE &H4500,A2 ' SET IN VALUE +560 EXEC LA ' RUN MACHINE CODE +570 CC=PEEK(&H4501) ' GET CC-REGISTER 590 ' CREATE BITS 600 T = CC -610 B7$=".":IF T AND 128 THEN B7$="E" -620 B6$=".":IF T AND 64 THEN B6$="F" -630 B5$=".":IF T AND 32 THEN B5$="H" -640 B4$=".":IF T AND 16 THEN B4$="I" -650 B3$=".":IF T AND 8 THEN B3$="N" -660 B2$=".":IF T AND 4 THEN B2$="Z" -670 B1$=".":IF T AND 2 THEN B1$="V" -680 B0$=".":IF T AND 1 THEN B0$="C" -690 PRINT "A=";RIGHT$(" "+STR$(A3),4);" CC=$";RIGHT$(" "+HEX$(CC),2);":";B7$;B6$;B5$;B4$;B3$;B2$;B1$;B0$ +610 B7$="E":IF T AND 128 THEN B7$="e" +620 B6$="F":IF T AND 64 THEN B6$="f" +630 B5$="H":IF T AND 32 THEN B5$="h" +640 B4$="I":IF T AND 16 THEN B4$="i" +650 B3$="N":IF T AND 8 THEN B3$="n" +660 B2$="Z":IF T AND 4 THEN B2$="z" +670 B1$="V":IF T AND 2 THEN B1$="v" +680 B0$="C":IF T AND 1 THEN B0$="c" +690 PRINT "in";LEFT$(STR$(A2)+" ",4);" > tst > cc=$";RIGHT$(" "+HEX$(CC),2);":";B7$;B6$;B5$;B4$;B3$;B2$;B1$;B0$ 700 NEXT I 710 GOTO 140 1000 ' MACHINE CODE IN HEX From bf05deebeb041e1184dd94d914edf3e25692ef36 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Tue, 8 Jul 2014 14:12:26 +0200 Subject: [PATCH 104/151] WIP: FPA0 test --- FloatingPoint/test_FPA0.bas | 49 +++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100755 FloatingPoint/test_FPA0.bas diff --git a/FloatingPoint/test_FPA0.bas b/FloatingPoint/test_FPA0.bas new file mode 100755 index 00000000..5939eb2f --- /dev/null +++ b/FloatingPoint/test_FPA0.bas @@ -0,0 +1,49 @@ +1 PRINT:PRINT "TEST FLOATS IN FPA0 V0.1" +2 PRINT "(GPL V3 OR ABOVE)" +3 PRINT:PRINT "COPYLEFT (C) 2014 JENS DIEMER":PRINT +11 COUNT=13 +20 LA=&H4000 ' LOAD / EXECUTE ADDRESS +25 PRINT "POKE MACHINE CODE TO: $";HEX$(LA) +30 PA = LA ' START ADDRESS FOR POKE +50 READ HB$ ' HEX CONSTANTS +60 IF HB$="END" THEN 100 +65 V=VAL("&H"+HB$) +70 POKE PA,V ' POKE VALUE INTO MEMORY +75 'PRINT "POKE $";HEX$(V);" AT $";HEX$(PA) +80 PA = PA + 1 ' INCREMENT POKE ADDRESS +90 GOTO 50 +100 PRINT "LOADED, END ADDRESS IS: $"; HEX$(PA-1) +110 PRINT:INPUT "INPUT START VALUE (DEZ)";D$ +115 IF D$="" THEN 20000 ELSE D=VAL(D$) +130 GOTO 500 +140 PRINT "UP/DOWN OR ANYKEY FOR NEW VALUE"; +150 I$ = INKEY$:IF I$="" THEN 150 +160 IF I$=CHR$(&H5E) THEN D=D2-(COUNT*2) : GOTO 500 ' UP KEYPRESS +170 IF I$=CHR$(&H0A) THEN D=D2 : GOTO 500 ' DOWN KEYPRESS +180 GOTO 110 ' NOT UP/DOWN +500 CLS:PRINT "D=";D +550 FOR I = 0 TO COUNT +551 D2=(D+I) 'AND &HFFFF +552 'PRINT "SET D=";D2 +553 POKE &H4500,D2 ' SET START VALUE +554 'PRINT "EXEC MACHINE CODE" +560 EXEC LA +565 'PRINT "GET VALUES FROM FPA0" +570 EX =PEEK(&H004F) ' FPA0 EXPONENT +580 MP =PEEK(&H0050) ' FPA0 MS +580 NMS =PEEK(&H0051) ' FPA0 NMS +580 NLS =PEEK(&H0052) ' FPA0 NLS +580 LS =PEEK(&H0053) ' FPA0 LS +580 SIGN=PEEK(&H0054) ' FPA0 SIGN +690 PRINT "D=";RIGHT$(" "+STR$(D2),4);" FPA0=$"+HEX$(EX)+" $"+HEX$(MS)+" $"+HEX$(NMS)+" $"+HEX$(NLS)+" $"+HEX$(LS)+" $"+HEX$(SIGN) +700 NEXT I +710 GOTO 140 +1000 ' MACHINE CODE IN HEX +1010 ' LDD $4500 +1020 DATA FC,45,00 +1030 ' JSR $8C37 ; ADD D TO FPA0 +1050 DATA BD,8C,37 +10000 ' RTS +10010 DATA 39 +10020 DATA END +20000 PRINT:PRINT "BYE" From c8be570f02745b1c3dff0dac83ddb6dfcfb41182 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Wed, 9 Jul 2014 10:30:15 +0200 Subject: [PATCH 105/151] WIP: xroar trace filter script. --- misc/filter_xroar_trace.py | 70 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 misc/filter_xroar_trace.py diff --git a/misc/filter_xroar_trace.py b/misc/filter_xroar_trace.py new file mode 100644 index 00000000..ef0b764e --- /dev/null +++ b/misc/filter_xroar_trace.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python +# encoding:utf-8 + +""" + :created: 2014 by Jens Diemer - www.jensdiemer.de + :license: GNU GPL v3 or above, see LICENSE for more details. +""" + +import os +import time +import sys + +XROAR_TRACE = os.path.expanduser("~/xroar_trace.txt") + +class XroarTraceFilter(object): + def __init__(self, filename): + self.filename = filename + self.addr_stat = self.stat_trace() + + def stat_trace(self): + print "Analyze %s..." % self.filename + stat = {} + next_update = time.time() + 1 + with open(self.filename, "r") as f: + for line in f: + if time.time() > next_update: + print "Analyzed %i lines..." % len(stat) + # sys.stdout.write(".") + # sys.stdout.flush() + next_update = time.time() + 1 + + addr = line.split("|", 1)[0] + stat.setdefault(addr, 0) + stat[addr] += 1 + + print "Analyzed %i lines, complete." % len(stat) + print + return stat + + def display_addr_stat(self, display_max=None): + for no, data in enumerate(sorted(self.addr_stat.items(), key=lambda x: x[1], reverse=True)): + if display_max and no >= display_max: + break + print "Address: %s exist %s times" % data + + def filter(self, max_count=10): + with open(self.filename, "r") as f: + skip_count = 0 + for line in f: + addr = line.split("|", 1)[0] + addr_count = self.addr_stat[addr] + if addr_count > max_count: + skip_count += 1 + continue + if skip_count != 0: + print "... [Skip %i lines] ..." % skip_count + skip_count = 0 + print line.strip() + + + +xt = XroarTraceFilter(XROAR_TRACE) +xt.display_addr_stat( + display_max=100 +) +xt.filter( + max_count=10 +) + + From d555159b1a0df7a4063b250d6f1cdbd7763f0f14 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Wed, 9 Jul 2014 12:14:39 +0200 Subject: [PATCH 106/151] Add CLI to xroar filter script and add README --- README.creole | 4 +- misc/README.creole | 142 ++++++++++++++++++++++++++++++++++++ misc/filter_xroar_trace.py | 145 ++++++++++++++++++++++++++----------- 3 files changed, 249 insertions(+), 42 deletions(-) create mode 100644 misc/README.creole mode change 100644 => 100755 misc/filter_xroar_trace.py diff --git a/README.creole b/README.creole index 34dc47b1..5a171f73 100755 --- a/README.creole +++ b/README.creole @@ -2,12 +2,14 @@ Some Python/BASIC tools/scripts around Dragon32/64 / CoCo homecomputer. -All script are copyleft 2013 by Jens Diemer and license unter GNU GPL v3 or above, see LICENSE for more details. +All script are copyleft 2013-2014 by Jens Diemer and license unter GNU GPL v3 or above, see LICENSE for more details. === Python scripts: * PyDC - Convert dragon 32 Cassetts WAV files into plain text: ** https://github.com/jedie/PyDragon32/tree/master/PyDC +* Filter Xroar traces: +** https://github.com/jedie/PyDragon32/tree/master/misc === BASIC programms: diff --git a/misc/README.creole b/misc/README.creole new file mode 100644 index 00000000..00be7ec7 --- /dev/null +++ b/misc/README.creole @@ -0,0 +1,142 @@ + +== filter_xroar_trace.py + +Filter Xroar trace files. + +The idea is simple: Skip lines with addresses that are calles very often, e.g.: +* The "how many RAM is installed" ROM routine +* The BASIC Interpreter idle loop + +=== usage + +{{{ +$ python filter_xroar_trace.py --help +usage: filter_xroar_trace.py [-h] [--display [MAX]] [--filter [MAX]] + infile [outfile] + +Filter Xroar traces + +positional arguments: + infile Xroar trace file. + outfile If given: write output in a new file else: Display it. + +optional arguments: + -h, --help show this help message and exit + --display [MAX] Display statistics how often a address is called. + --filter [MAX] Filter the trace: skip addresses that called more than + given count. +}}} + +=== examples + + +==== Display statistics +Just display the most often called addresses: +{{{ +$ python filter_xroar_trace.py --display=10 ~/xroar_trace.txt + +Read /home/jens/xroar_trace.txt... + +Analyzed 43512 op calls, complete. + +The tracefile contains 64 unique addresses. + +List of the 10 most called addresses: +Address: $b3d1 called 4851 times. +Address: $b3d5 called 4851 times. +Address: $b3d3 called 4851 times. +Address: $b3ca called 4851 times. +Address: $b3cc called 4851 times. +Address: $b3cf called 4851 times. +Address: $b3cd called 4851 times. +Address: $b3d7 called 4850 times. +Address: $b3c1 called 1025 times. +Address: $b3bf called 1025 times. +}}} + + +==== Filter +Filter the trace and list only addresses that called not more than one time: +{{{ +$ python filter_xroar_trace.py --filter=1 ~/xroar_trace.txt + +Read /home/jens/xroar_trace.txt... + +Analyzed 43512 op calls, complete. + +The tracefile contains 64 unique addresses. + + +Filter with 1: +fffe| b3b4 [RESET] +b3b4| 318ce4 LEAY -$1c,PCR cc=50 a=00 b=00 dp=00 x=0000 y=b39b u=0000 s=0000 +b3b7| 7e8000 JMP $8000 cc=50 a=00 b=00 dp=00 x=0000 y=b39b u=0000 s=0000 +8000| 7ebb40 JMP $bb40 cc=50 a=00 b=00 dp=00 x=0000 y=b39b u=0000 s=0000 +bb40| cc0034 LDD #$0034 cc=50 a=00 b=34 dp=00 x=0000 y=b39b u=0000 s=0000 +bb43| 8eff00 LDX #$ff00 cc=58 a=00 b=34 dp=00 x=ff00 y=b39b u=0000 s=0000 +bb46| a701 STA 1,X cc=54 a=00 b=34 dp=00 x=ff00 y=b39b u=0000 s=0000 +bb48| a703 STA 3,X cc=54 a=00 b=34 dp=00 x=ff00 y=b39b u=0000 s=0000 +bb4a| a784 STA ,X cc=54 a=00 b=34 dp=00 x=ff00 y=b39b u=0000 s=0000 +bb4c| 43 COMA cc=59 a=ff b=34 dp=00 x=ff00 y=b39b u=0000 s=0000 +bb4d| a702 STA 2,X cc=59 a=ff b=34 dp=00 x=ff00 y=b39b u=0000 s=0000 +bb4f| e701 STB 1,X cc=51 a=ff b=34 dp=00 x=ff00 y=b39b u=0000 s=0000 +bb51| e703 STB 3,X cc=51 a=ff b=34 dp=00 x=ff00 y=b39b u=0000 s=0000 +bb53| 8eff20 LDX #$ff20 cc=59 a=ff b=34 dp=00 x=ff20 y=b39b u=0000 s=0000 +bb56| 6f01 CLR 1,X cc=54 a=ff b=34 dp=00 x=ff20 y=b39b u=0000 s=0000 +bb58| 6f03 CLR 3,X cc=54 a=ff b=34 dp=00 x=ff20 y=b39b u=0000 s=0000 +bb5a| 4a DECA cc=58 a=fe b=34 dp=00 x=ff20 y=b39b u=0000 s=0000 +bb5b| a784 STA ,X cc=58 a=fe b=34 dp=00 x=ff20 y=b39b u=0000 s=0000 +bb5d| 86f8 LDA #$f8 cc=58 a=f8 b=34 dp=00 x=ff20 y=b39b u=0000 s=0000 +bb5f| a702 STA 2,X cc=58 a=f8 b=34 dp=00 x=ff20 y=b39b u=0000 s=0000 +bb61| e701 STB 1,X cc=50 a=f8 b=34 dp=00 x=ff20 y=b39b u=0000 s=0000 +bb63| e703 STB 3,X cc=50 a=f8 b=34 dp=00 x=ff20 y=b39b u=0000 s=0000 +bb65| 6f84 CLR ,X cc=54 a=f8 b=34 dp=00 x=ff20 y=b39b u=0000 s=0000 +bb67| 6f02 CLR 2,X cc=54 a=f8 b=34 dp=00 x=ff20 y=b39b u=0000 s=0000 +bb69| a602 LDA 2,X cc=54 a=00 b=34 dp=00 x=ff20 y=b39b u=0000 s=0000 +bb6b| 8effc0 LDX #$ffc0 cc=58 a=00 b=34 dp=00 x=ffc0 y=b39b u=0000 s=0000 +bb6e| c610 LDB #$10 cc=50 a=00 b=10 dp=00 x=ffc0 y=b39b u=0000 s=0000 +... [Skip 48 lines] ... +bb75| f7ffc9 STB $ffc9 cc=54 a=00 b=00 dp=00 x=ffe0 y=b39b u=0000 s=0000 +bb78| 8504 BITA #$04 cc=54 a=00 b=00 dp=00 x=ffe0 y=b39b u=0000 s=0000 +bb7a| 2705 BEQ $bb81 cc=54 a=00 b=00 dp=00 x=ffe0 y=b39b u=0000 s=0000 +bb81| f7ffdd STB $ffdd cc=54 a=00 b=00 dp=00 x=ffe0 y=b39b u=0000 s=0000 +bb84| 1f9b TFR B,DP cc=54 a=00 b=00 dp=00 x=ffe0 y=b39b u=0000 s=0000 +bb86| 1f25 TFR Y,PC cc=54 a=00 b=00 dp=00 x=ffe0 y=b39b u=0000 s=0000 +b39b| 10ce03d7 LDS #$03d7 cc=50 a=00 b=00 dp=00 x=ffe0 y=b39b u=0000 s=03d7 +b39f| 8637 LDA #$37 cc=50 a=37 b=00 dp=00 x=ffe0 y=b39b u=0000 s=03d7 +b3a1| b7ff23 STA $ff23 cc=50 a=37 b=00 dp=00 x=ffe0 y=b39b u=0000 s=03d7 +b3a4| 9671 LDA <$71 cc=58 a=ff b=00 dp=00 x=ffe0 y=b39b u=0000 s=03d7 +b3a6| 8155 CMPA #$55 cc=58 a=ff b=00 dp=00 x=ffe0 y=b39b u=0000 s=03d7 +b3a8| 2610 BNE $b3ba cc=58 a=ff b=00 dp=00 x=ffe0 y=b39b u=0000 s=03d7 +b3ba| 8e0401 LDX #$0401 cc=50 a=ff b=00 dp=00 x=0401 y=b39b u=0000 s=03d7 +... [Skip 3075 lines] ... +b3c3| bdba77 JSR $ba77 cc=54 a=ff b=00 dp=00 x=0000 y=b39b u=0000 s=03d5 +ba77| c660 LDB #$60 cc=50 a=ff b=60 dp=00 x=0000 y=b39b u=0000 s=03d5 +ba79| 8e0400 LDX #$0400 cc=50 a=ff b=60 dp=00 x=0400 y=b39b u=0000 s=03d5 +ba7c| 9f88 STX <$88 cc=50 a=ff b=60 dp=00 x=0400 y=b39b u=0000 s=03d5 +... [Skip 1536 lines] ... +ba85| 39 RTS cc=50 a=ff b=60 dp=00 x=0600 y=b39b u=0000 s=03d7 +b3c6| 6f80 CLR ,X+ cc=54 a=ff b=60 dp=00 x=0601 y=b39b u=0000 s=03d7 +b3c8| 9f19 STX <$19 cc=50 a=ff b=60 dp=00 x=0601 y=b39b u=0000 s=03d7 + +43466 lines was filtered. +}}} + + +==== Filter into new file + +{{{ +$ python filter_xroar_trace.py --filter=10 ~/xroar_trace.txt filtered_trace.txt + +Read /home/jens/xroar_trace.txt... + +Analyzed 43512 op calls, complete. + +The tracefile contains 64 unique addresses. + + +Filter with 10: +Create file 'filtered_trace.txt'... + +43466 lines was filtered. +}}} \ No newline at end of file diff --git a/misc/filter_xroar_trace.py b/misc/filter_xroar_trace.py old mode 100644 new mode 100755 index ef0b764e..04acf2bd --- a/misc/filter_xroar_trace.py +++ b/misc/filter_xroar_trace.py @@ -2,6 +2,10 @@ # encoding:utf-8 """ + Filter Xroar trace files. + + see README for more information. + :created: 2014 by Jens Diemer - www.jensdiemer.de :license: GNU GPL v3 or above, see LICENSE for more details. """ @@ -9,62 +13,121 @@ import os import time import sys +import argparse -XROAR_TRACE = os.path.expanduser("~/xroar_trace.txt") class XroarTraceFilter(object): - def __init__(self, filename): - self.filename = filename + def __init__(self, infile, outfile): + self.infile = infile + self.outfile = outfile self.addr_stat = self.stat_trace() def stat_trace(self): - print "Analyze %s..." % self.filename + print + print "Read %s..." % self.infile.name + print stat = {} next_update = time.time() + 1 - with open(self.filename, "r") as f: - for line in f: - if time.time() > next_update: - print "Analyzed %i lines..." % len(stat) - # sys.stdout.write(".") - # sys.stdout.flush() - next_update = time.time() + 1 - - addr = line.split("|", 1)[0] - stat.setdefault(addr, 0) - stat[addr] += 1 - - print "Analyzed %i lines, complete." % len(stat) + line_no = 0 # e.g. empty file + for line_no, line in enumerate(self.infile): + if time.time() > next_update: + print "Analyzed %i op calls..." % line_no + next_update = time.time() + 1 + + addr = line.split("|", 1)[0] + stat.setdefault(addr, 0) + stat[addr] += 1 + + print "Analyzed %i op calls, complete." % line_no + print + print "The tracefile contains %i unique addresses." % len(stat) print return stat def display_addr_stat(self, display_max=None): + if display_max is None: + print "List of all called addresses:" + else: + print "List of the %i most called addresses:" % display_max + for no, data in enumerate(sorted(self.addr_stat.items(), key=lambda x: x[1], reverse=True)): - if display_max and no >= display_max: + if display_max is not None and no >= display_max: break - print "Address: %s exist %s times" % data + print "Address: $%s called %s times." % data def filter(self, max_count=10): - with open(self.filename, "r") as f: - skip_count = 0 - for line in f: - addr = line.split("|", 1)[0] - addr_count = self.addr_stat[addr] - if addr_count > max_count: - skip_count += 1 - continue - if skip_count != 0: - print "... [Skip %i lines] ..." % skip_count - skip_count = 0 - print line.strip() - - - -xt = XroarTraceFilter(XROAR_TRACE) -xt.display_addr_stat( - display_max=100 -) -xt.filter( - max_count=10 -) + print + print "Filter with %i:" % max_count + + to_stdout = self.outfile.name == "" # FIXME + if not to_stdout: + print "Create file %r..." % self.outfile.name + + assert self.infile.name != self.outfile.name # FIXME + + total_skiped_lines = 0 + skip_count = 0 + self.infile.seek(0) + for line in self.infile: + addr = line.split("|", 1)[0] + addr_count = self.addr_stat[addr] + if addr_count > max_count: + total_skiped_lines += 1 + skip_count += 1 + continue + if skip_count != 0: + self.outfile.write( + "... [Skip %i lines] ...\n" % skip_count + ) + skip_count = 0 + self.outfile.write(line) + + if not to_stdout: + self.outfile.close() + + print + print "%i lines was filtered." % total_skiped_lines + + +def main(args): + xt = XroarTraceFilter(args.infile, args.outfile) + if "display" in args: + xt.display_addr_stat( + display_max=args.display + ) + if args.filter: + xt.filter( + max_count=args.filter + ) + + +def get_cli_args(): + parser = argparse.ArgumentParser(description="Filter Xroar traces") + parser.add_argument("infile", + type=argparse.FileType("r"), + help="Xroar trace file." + ) + parser.add_argument("outfile", nargs="?", + type=argparse.FileType("w"), + default=sys.stdout, + help="If given: write output in a new file else: Display it." + ) + parser.add_argument("--display", metavar="MAX", + type=int, default=argparse.SUPPRESS, + nargs="?", + help="Display statistics how often a address is called.", + ) + parser.add_argument("--filter", metavar="MAX", + type=int, + nargs="?", + help="Filter the trace: skip addresses that called more than given count.", + ) + args = parser.parse_args() + return args + + +if __name__ == '__main__': + args = get_cli_args() + main(args) From 7dc9312dcf3ef180f19155cb426f04f80773b8aa Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Wed, 9 Jul 2014 19:13:45 +0200 Subject: [PATCH 107/151] add live filter --- misc/README.creole | 15 +++++ misc/filter_xroar_trace.py | 120 +++++++++++++++++++++++-------------- 2 files changed, 89 insertions(+), 46 deletions(-) diff --git a/misc/README.creole b/misc/README.creole index 00be7ec7..905046e1 100644 --- a/misc/README.creole +++ b/misc/README.creole @@ -29,6 +29,21 @@ optional arguments: === examples +==== Live filter +Create a startup trace: +{{{ +$ xroar -trace > startup_trace.txt +}}} +Let xroar start the machine e.g.: until the prompt is blicking. +Then quit xroar. + +Start again with the created **startup_trace.txt** file: +{{{ +$ xroar -trace | python filter_xroar_trace.py --loop-filter startup_trace.txt +}}} + +Now you will see only trace output for addresses that aren't in **startup_trace.txt** ;) + ==== Display statistics Just display the most often called addresses: diff --git a/misc/filter_xroar_trace.py b/misc/filter_xroar_trace.py index 04acf2bd..6b17a3b3 100755 --- a/misc/filter_xroar_trace.py +++ b/misc/filter_xroar_trace.py @@ -20,61 +20,76 @@ class XroarTraceFilter(object): def __init__(self, infile, outfile): self.infile = infile self.outfile = outfile - self.addr_stat = self.stat_trace() - - def stat_trace(self): - print - print "Read %s..." % self.infile.name - print - stat = {} - next_update = time.time() + 1 + + def load_tracefile(self, f): + sys.stderr.write( + "\nRead %s...\n\n" % f.name + ) + addr_stat = {} + next_update = time.time() + 0.5 line_no = 0 # e.g. empty file - for line_no, line in enumerate(self.infile): + for line_no, line in enumerate(f): if time.time() > next_update: - print "Analyzed %i op calls..." % line_no - next_update = time.time() + 1 + sys.stderr.write( + "\rAnalyzed %i op calls..." % line_no + ) + sys.stderr.flush() + next_update = time.time() + 0.5 - addr = line.split("|", 1)[0] - stat.setdefault(addr, 0) - stat[addr] += 1 + addr = line[:4] + addr_stat.setdefault(addr, 0) + addr_stat[addr] += 1 - print "Analyzed %i op calls, complete." % line_no - print - print "The tracefile contains %i unique addresses." % len(stat) - print - return stat + f.seek(0) # if also used in self.filter() + + sys.stderr.write( + "\rAnalyzed %i op calls, complete.\n" % line_no + ) + sys.stderr.write( + "\nThe tracefile contains %i unique addresses.\n" % len(addr_stat) + ) + return addr_stat - def display_addr_stat(self, display_max=None): + def display_addr_stat(self, addr_stat, display_max=None): if display_max is None: - print "List of all called addresses:" + sys.stdout.write( + "\nList of all called addresses:\n" + ) else: - print "List of the %i most called addresses:" % display_max + sys.stdout.write( + "List of the %i most called addresses:\n" % display_max + ) for no, data in enumerate(sorted(self.addr_stat.items(), key=lambda x: x[1], reverse=True)): if display_max is not None and no >= display_max: break - print "Address: $%s called %s times." % data - - def filter(self, max_count=10): - print - print "Filter with %i:" % max_count - - to_stdout = self.outfile.name == "" # FIXME - if not to_stdout: - print "Create file %r..." % self.outfile.name - - assert self.infile.name != self.outfile.name # FIXME + sys.stdout.write( + "\tAddress %s called %s times.\n" % data + ) + def get_max_count_filter(self, addr_stat, max_count=10): + sys.stderr.write( + "Filter addresses with more than %i calls:\n" % max_count + ) + addr_filter = {} + for addr, count in self.addr_stat.items(): + if count >= max_count: + addr_filter[addr] = count + return addr_filter + + def filter(self, addr_filter): + sys.stderr.write( + "Filter %i addresses.\n" % len(addr_filter) + ) total_skiped_lines = 0 skip_count = 0 - self.infile.seek(0) for line in self.infile: - addr = line.split("|", 1)[0] - addr_count = self.addr_stat[addr] - if addr_count > max_count: + addr = line[:4] + if addr in addr_filter: total_skiped_lines += 1 skip_count += 1 continue + if skip_count != 0: self.outfile.write( "... [Skip %i lines] ...\n" % skip_count @@ -82,30 +97,38 @@ def filter(self, max_count=10): skip_count = 0 self.outfile.write(line) - if not to_stdout: - self.outfile.close() - - print - print "%i lines was filtered." % total_skiped_lines + self.outfile.close() + sys.stderr.write( + "%i lines was filtered.\n" % total_skiped_lines + ) def main(args): xt = XroarTraceFilter(args.infile, args.outfile) + if args.loop_filter: + addr_stat = xt.load_tracefile(args.loop_filter) + xt.filter(addr_filter=addr_stat) + if "display" in args: - xt.display_addr_stat( + addr_stat = xt.load_tracefile(args.infile) + xt.display_addr_stat(addr_stat, display_max=args.display ) + if args.filter: - xt.filter( + addr_stat = xt.load_tracefile(args.infile) + addr_filter = xt.get_max_count_filter(addr_stat, max_count=args.filter ) + xt.filter(addr_filter) def get_cli_args(): parser = argparse.ArgumentParser(description="Filter Xroar traces") - parser.add_argument("infile", + parser.add_argument("infile", nargs="?", type=argparse.FileType("r"), - help="Xroar trace file." + default=sys.stdin, + help="Xroar trace file or stdin" ) parser.add_argument("outfile", nargs="?", type=argparse.FileType("w"), @@ -122,6 +145,11 @@ def get_cli_args(): nargs="?", help="Filter the trace: skip addresses that called more than given count.", ) + parser.add_argument("--loop-filter", metavar="FILENAME", + type=argparse.FileType("r"), + nargs="?", + help="Live Filter with given address file.", + ) args = parser.parse_args() return args From 3ee31f72a82644a0035036efeaf7bd298851d9f9 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Wed, 9 Jul 2014 23:16:31 +0200 Subject: [PATCH 108/151] add "add_info_in_trace.py" --- misc/README.creole | 36 +++++++++++ misc/add_info_in_trace.py | 129 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+) create mode 100644 misc/add_info_in_trace.py diff --git a/misc/README.creole b/misc/README.creole index 905046e1..b3152537 100644 --- a/misc/README.creole +++ b/misc/README.creole @@ -154,4 +154,40 @@ Filter with 10: Create file 'filtered_trace.txt'... 43466 lines was filtered. +}}} + + + + +== add_info_in_trace.py + +Add address info on Xroar trace lines. +Used informationfiles from: https://github.com/6809/rom-info + +e.g.: +{{{ +xroar -trace | python add_info_in_trace.py --infofile Dragon32.txt +# or better: +$ xroar -trace | python filter_xroar_trace.py --loop-filter startup_trace.txt | python add_info_in_trace.py --infofile Dragon32.txt +}}} + +example output: +{{{ +... [Skip 14 lines] ... +897a| 9e64 LDX <$64 cc=a1 a=ff b=fd dp=00 x=02e2 y=804b u=7fff s=7f2c | $897a: $8000-$9fff - CoCo - Extended Color BASIC ROM +897c| 9fa6 STX <$a6 cc=a1 a=ff b=fd dp=00 x=02e2 y=804b u=7fff s=7f2c | $897c: $8000-$9fff - CoCo - Extended Color BASIC ROM +897e| 39 RTS cc=a1 a=ff b=fd dp=00 x=02e2 y=804b u=7fff s=7f2e | $897e: $8000-$9fff - CoCo - Extended Color BASIC ROM +8897| 0f3f CLR <$3f cc=a4 a=ff b=fd dp=00 x=02e2 y=804b u=7fff s=7f2e | $8897: $8000-$9fff - CoCo - Extended Color BASIC ROM +8899| 9da5 JSR <$a5 cc=a4 a=ff b=fd dp=00 x=02e2 y=804b u=7fff s=7f2c | $8899: $8000-$9fff - CoCo - Extended Color BASIC ROM +00a5| b602e2 LDA $02e2 cc=a4 a=00 b=fd dp=00 x=02e2 y=804b u=7fff s=7f2c | $a5: $a5-$a7 - LDA >xxxx +00a8| 7ebb26 JMP $bb26 cc=a4 a=00 b=fd dp=00 x=02e2 y=804b u=7fff s=7f2c | $a8: $a8-$aa - JMP $BB26 +bb26| 813a CMPA #$3a cc=a9 a=00 b=fd dp=00 x=02e2 y=804b u=7fff s=7f2c | $bb26: $bb26-$bb34 - Jumped to from selfmodifying CHRGET routine at $009f +bb28| 240a BCC $bb34 cc=a9 a=00 b=fd dp=00 x=02e2 y=804b u=7fff s=7f2c | $bb28: $bb26-$bb34 - Jumped to from selfmodifying CHRGET routine at $009f +bb2a| 8120 CMPA #$20 cc=a9 a=00 b=fd dp=00 x=02e2 y=804b u=7fff s=7f2c | $bb2a: $bb26-$bb34 - Jumped to from selfmodifying CHRGET routine at $009f +bb2c| 2602 BNE $bb30 cc=a9 a=00 b=fd dp=00 x=02e2 y=804b u=7fff s=7f2c | $bb2c: $bb26-$bb34 - Jumped to from selfmodifying CHRGET routine at $009f +bb30| 8030 SUBA #$30 cc=a9 a=d0 b=fd dp=00 x=02e2 y=804b u=7fff s=7f2c | $bb30: $bb26-$bb34 - Jumped to from selfmodifying CHRGET routine at $009f +bb32| 80d0 SUBA #$d0 cc=a4 a=00 b=fd dp=00 x=02e2 y=804b u=7fff s=7f2c | $bb32: $bb26-$bb34 - Jumped to from selfmodifying CHRGET routine at $009f +bb34| 39 RTS cc=a4 a=00 b=fd dp=00 x=02e2 y=804b u=7fff s=7f2e | $bb34: $bb26-$bb34 - Jumped to from selfmodifying CHRGET routine at $009f +889b| 80ca SUBA #$ca cc=a1 a=36 b=fd dp=00 x=02e2 y=804b u=7fff s=7f2e | $889b: $8000-$9fff - CoCo - Extended Color BASIC ROM +889d| 2513 BCS $88b2 cc=a1 a=36 b=fd dp=00 x=02e2 y=804b u=7fff s=7f2e | $889d: $8000-$9fff - CoCo - Extended Color BASIC ROM }}} \ No newline at end of file diff --git a/misc/add_info_in_trace.py b/misc/add_info_in_trace.py new file mode 100644 index 00000000..cd0b0435 --- /dev/null +++ b/misc/add_info_in_trace.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python +# encoding:utf-8 + +""" + Filter Xroar trace files. + + see README for more information. + + :created: 2014 by Jens Diemer - www.jensdiemer.de + :license: GNU GPL v3 or above, see LICENSE for more details. +""" + +import os +import time +import sys +import argparse + +class MemoryInfo(object): + def __init__(self, rom_info_file): + self.mem_info = self._get_rom_info(rom_info_file) + + def eval_addr(self, addr): + addr = addr.strip("$") + return int(addr, 16) + + def _get_rom_info(self, rom_info_file): + sys.stderr.write( + "Read ROM Info file: %r\n" % rom_info_file.name + ) + rom_info = [] + for line in rom_info_file: + try: + addr_raw, comment = line.split(";", 1) + except ValueError: + continue + + try: + start_addr_raw, end_addr_raw = addr_raw.split("-") + except ValueError: + start_addr_raw = addr_raw + end_addr_raw = None + + start_addr = self.eval_addr(start_addr_raw) + if end_addr_raw: + end_addr = self.eval_addr(end_addr_raw) + else: + end_addr = start_addr + + rom_info.append( + (start_addr, end_addr, comment) + ) + return rom_info + + def get_shortest(self, addr): + shortest = None + size = sys.maxint + for start, end, txt in self.mem_info: + if not start <= addr <= end: + continue + + current_size = abs(end - start) + if current_size < size: + size = current_size + shortest = start, end, txt + + if shortest is None: + return "$%x: UNKNOWN" % addr + + start, end, txt = shortest + if start == end: + return "$%x: %s" % (addr, txt) + else: + return "$%x: $%x-$%x - %s" % (addr, start, end, txt) + + + + +class XroarTraceInfo(object): + def __init__(self, infile, outfile): + self.infile = infile + self.outfile = outfile + + def add_info(self, rom_info): + for line in self.infile: + addr = line[:4] + try: + addr = int(addr, 16) + except ValueError: + self.outfile.write(line) + continue + + addr_info = rom_info.get_shortest(addr) + self.outfile.write( + "%s | %s" % (line.strip(), addr_info) + ) + + + +def main(args): + xt = XroarTraceInfo(args.infile, args.outfile) + rom_info = MemoryInfo(args.infofile) + xt.add_info(rom_info) + + +def get_cli_args(): + parser = argparse.ArgumentParser(description="Add info to Xroar traces") + parser.add_argument("infile", nargs="?", + type=argparse.FileType("r"), + default=sys.stdin, + help="Xroar trace file or stdin" + ) + parser.add_argument("outfile", nargs="?", + type=argparse.FileType("w"), + default=sys.stdout, + help="If given: write output in a new file else: Display it." + ) + parser.add_argument("--infofile", metavar="FILENAME", + type=argparse.FileType("r"), + help="ROM Info file from: https://github.com/6809/rom-info ;)", + ) + args = parser.parse_args() + return args + + +if __name__ == '__main__': + args = get_cli_args() + main(args) + + From 668c51b0e122f22dd59dfd636ddf5ee15ae91df5 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Thu, 10 Jul 2014 09:25:00 +0200 Subject: [PATCH 109/151] Use a simple cache for big speedup. --- misc/add_info_in_trace.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/misc/add_info_in_trace.py b/misc/add_info_in_trace.py index cd0b0435..b9c302e4 100644 --- a/misc/add_info_in_trace.py +++ b/misc/add_info_in_trace.py @@ -18,6 +18,7 @@ class MemoryInfo(object): def __init__(self, rom_info_file): self.mem_info = self._get_rom_info(rom_info_file) + self._cache = {} def eval_addr(self, addr): addr = addr.strip("$") @@ -49,9 +50,17 @@ def _get_rom_info(self, rom_info_file): rom_info.append( (start_addr, end_addr, comment) ) + sys.stderr.write( + "ROM Info file: %r readed.\n" % rom_info_file.name + ) return rom_info def get_shortest(self, addr): + try: + return self._cache[addr] + except KeyError: + pass + shortest = None size = sys.maxint for start, end, txt in self.mem_info: @@ -68,11 +77,11 @@ def get_shortest(self, addr): start, end, txt = shortest if start == end: - return "$%x: %s" % (addr, txt) + info = "$%x: %s" % (addr, txt) else: - return "$%x: $%x-$%x - %s" % (addr, start, end, txt) - - + info = "$%x: $%x-$%x - %s" % (addr, start, end, txt) + self._cache[addr] = info + return info class XroarTraceInfo(object): From 0ec98fe56bfbae29629d888a99f186bdeea66af7 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Thu, 10 Jul 2014 09:48:44 +0200 Subject: [PATCH 110/151] Display status on live filtering --- misc/add_info_in_trace.py | 28 ++++++++++++++++++++++++---- misc/filter_xroar_trace.py | 14 +++++++++++++- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/misc/add_info_in_trace.py b/misc/add_info_in_trace.py index b9c302e4..29f294a6 100644 --- a/misc/add_info_in_trace.py +++ b/misc/add_info_in_trace.py @@ -29,7 +29,15 @@ def _get_rom_info(self, rom_info_file): "Read ROM Info file: %r\n" % rom_info_file.name ) rom_info = [] - for line in rom_info_file: + next_update = time.time() + 0.5 + for line_no, line in enumerate(rom_info_file): + if time.time() > next_update: + sys.stderr.write( + "\rRead %i lines..." % line_no + ) + sys.stderr.flush() + next_update = time.time() + 0.5 + try: addr_raw, comment = line.split(";", 1) except ValueError: @@ -90,7 +98,19 @@ def __init__(self, infile, outfile): self.outfile = outfile def add_info(self, rom_info): - for line in self.infile: + last_line_no = 0 + next_update = time.time() + 1 + for line_no, line in enumerate(self.infile): + if time.time() > next_update: + sys.stderr.write( + "\rRead %i lines (%i/sec.)..." % ( + line_no, (line_no - last_line_no) + ) + ) + sys.stderr.flush() + last_line_no = line_no + next_update = time.time() + 1 + addr = line[:4] try: addr = int(addr, 16) @@ -100,8 +120,8 @@ def add_info(self, rom_info): addr_info = rom_info.get_shortest(addr) self.outfile.write( - "%s | %s" % (line.strip(), addr_info) - ) + "%s | %s" % (line.strip(), addr_info) + ) diff --git a/misc/filter_xroar_trace.py b/misc/filter_xroar_trace.py index 6b17a3b3..4b72b4e3 100755 --- a/misc/filter_xroar_trace.py +++ b/misc/filter_xroar_trace.py @@ -83,7 +83,19 @@ def filter(self, addr_filter): ) total_skiped_lines = 0 skip_count = 0 - for line in self.infile: + last_line_no = 0 + next_update = time.time() + 1 + for line_no, line in enumerate(self.infile): + if time.time() > next_update: + sys.stderr.write( + "\rFilter %i lines (%i/sec.)..." % ( + line_no, (line_no - last_line_no) + ) + ) + sys.stderr.flush() + last_line_no = line_no + next_update = time.time() + 1 + addr = line[:4] if addr in addr_filter: total_skiped_lines += 1 From b48d081ed127e8911169cfdfa13f4d3f8f2c2151 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Thu, 10 Jul 2014 10:58:58 +0200 Subject: [PATCH 111/151] bugfix --- misc/add_info_in_trace.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/misc/add_info_in_trace.py b/misc/add_info_in_trace.py index 29f294a6..28a51720 100644 --- a/misc/add_info_in_trace.py +++ b/misc/add_info_in_trace.py @@ -81,13 +81,13 @@ def get_shortest(self, addr): shortest = start, end, txt if shortest is None: - return "$%x: UNKNOWN" % addr - - start, end, txt = shortest - if start == end: - info = "$%x: %s" % (addr, txt) + info = "$%x: UNKNOWN" % addr else: - info = "$%x: $%x-$%x - %s" % (addr, start, end, txt) + start, end, txt = shortest + if start == end: + info = "$%x: %s" % (addr, txt) + else: + info = "$%x: $%x-$%x - %s" % (addr, start, end, txt) self._cache[addr] = info return info @@ -120,7 +120,7 @@ def add_info(self, rom_info): addr_info = rom_info.get_shortest(addr) self.outfile.write( - "%s | %s" % (line.strip(), addr_info) + "%s | %s\n" % (line.strip(), addr_info) ) From 0eedee7f603f777b464ceced0176cb96c087e1d0 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Thu, 10 Jul 2014 11:48:03 +0200 Subject: [PATCH 112/151] add "filter_xroar_trace.py --unique" function --- misc/README.creole | 5 ++- misc/filter_xroar_trace.py | 63 +++++++++++++++++++++++++++++++++++++- 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/misc/README.creole b/misc/README.creole index b3152537..1cb24bc7 100644 --- a/misc/README.creole +++ b/misc/README.creole @@ -32,11 +32,14 @@ optional arguments: ==== Live filter Create a startup trace: {{{ -$ xroar -trace > startup_trace.txt +$ xroar -trace | python filter_xroar_trace.py --unique | tee startup_trace.txt }}} Let xroar start the machine e.g.: until the prompt is blicking. Then quit xroar. +The **--unique** filtering skips all called addresses. So you get a small tracefile. + + Start again with the created **startup_trace.txt** file: {{{ $ xroar -trace | python filter_xroar_trace.py --loop-filter startup_trace.txt diff --git a/misc/filter_xroar_trace.py b/misc/filter_xroar_trace.py index 4b72b4e3..2b3e9cde 100755 --- a/misc/filter_xroar_trace.py +++ b/misc/filter_xroar_trace.py @@ -25,7 +25,7 @@ def load_tracefile(self, f): sys.stderr.write( "\nRead %s...\n\n" % f.name ) - addr_stat = {} + addr_stat = {} # TODO: Use collections.Counter next_update = time.time() + 0.5 line_no = 0 # e.g. empty file for line_no, line in enumerate(f): @@ -50,6 +50,57 @@ def load_tracefile(self, f): ) return addr_stat + def unique(self): + sys.stderr.write( + "\nunique %s in %s...\n\n" % (self.infile.name, self.outfile.name) + ) + unique_addr = set() + total_skiped_lines = 0 + skip_count = 0 + last_line_no = 0 + next_update = time.time() + 1 + stat_out = False + for line_no, line in enumerate(self.infile): + if time.time() > next_update: + self.outfile.flush() + if stat_out: + sys.stderr.write("\r") + else: + sys.stderr.write("\n") + sys.stderr.write( + "In %i lines (%i/sec.) are %i unique address calls..." % ( + line_no, (line_no - last_line_no), len(unique_addr) + ) + ) + stat_out = True + sys.stderr.flush() + last_line_no = line_no + next_update = time.time() + 1 + + addr = line[:4] + if addr in unique_addr: + total_skiped_lines += 1 + skip_count += 1 + continue + + unique_addr.add(addr) + + if skip_count != 0: + if stat_out: + # Skip info should not in the same line after stat info + sys.stderr.write("\n") + self.outfile.write( + "... [Skip %i lines] ...\n" % skip_count + ) + skip_count = 0 + self.outfile.write(line) + stat_out = False + + self.outfile.close() + sys.stderr.write( + "%i lines was filtered.\n" % total_skiped_lines + ) + def display_addr_stat(self, addr_stat, display_max=None): if display_max is None: sys.stdout.write( @@ -115,8 +166,14 @@ def filter(self, addr_filter): ) + def main(args): xt = XroarTraceFilter(args.infile, args.outfile) + + if args.unique: + xt.unique() + return + if args.loop_filter: addr_stat = xt.load_tracefile(args.loop_filter) xt.filter(addr_filter=addr_stat) @@ -157,6 +214,10 @@ def get_cli_args(): nargs="?", help="Filter the trace: skip addresses that called more than given count.", ) + parser.add_argument("--unique", + action="store_true", + help="Read infile and store in outfile only unique addresses.", + ) parser.add_argument("--loop-filter", metavar="FILENAME", type=argparse.FileType("r"), nargs="?", From 100b2d8e7d5c3eda30be895def158fe2db3de8e8 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Thu, 10 Jul 2014 17:27:41 +0200 Subject: [PATCH 113/151] new filter: "filter_xroar_trace.py --start-stop=8c37-91c1" --- misc/README.creole | 7 +++ misc/add_info_in_trace.py | 2 +- misc/filter_xroar_trace.py | 101 +++++++++++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+), 1 deletion(-) diff --git a/misc/README.creole b/misc/README.creole index 1cb24bc7..556b149d 100644 --- a/misc/README.creole +++ b/misc/README.creole @@ -47,6 +47,13 @@ $ xroar -trace | python filter_xroar_trace.py --loop-filter startup_trace.txt Now you will see only trace output for addresses that aren't in **startup_trace.txt** ;) +==== Start / Stop filtering +If you know the entry point in ROM and whant only see a area as a trace used this. + +e.g. See only traces after address **$8c37** was called and then until address **$91c1** called: +{{{ +$ xroar -trace | python filter_xroar_trace.py --start-stop=8c37-91c1 | tee routine_trace.txt +}}} ==== Display statistics Just display the most often called addresses: diff --git a/misc/add_info_in_trace.py b/misc/add_info_in_trace.py index 28a51720..7083a47e 100644 --- a/misc/add_info_in_trace.py +++ b/misc/add_info_in_trace.py @@ -56,7 +56,7 @@ def _get_rom_info(self, rom_info_file): end_addr = start_addr rom_info.append( - (start_addr, end_addr, comment) + (start_addr, end_addr, comment.strip()) ) sys.stderr.write( "ROM Info file: %r readed.\n" % rom_info_file.name diff --git a/misc/filter_xroar_trace.py b/misc/filter_xroar_trace.py index 2b3e9cde..7d7c1f2b 100755 --- a/misc/filter_xroar_trace.py +++ b/misc/filter_xroar_trace.py @@ -165,6 +165,86 @@ def filter(self, addr_filter): "%i lines was filtered.\n" % total_skiped_lines ) + def start_stop(self, start_addr, stop_addr): + sys.stderr.write( + "\nFilter starts with $%x and ends with $%x from %s in %s...\n\n" % ( + start_addr, stop_addr, + self.infile.name, self.outfile.name + ) + ) + + all_addresses = set() + passed_addresses = set() + + start_seperator = "\n ---- [ START $%x ] ---- \n" % start_addr + end_seperator = "\n ---- [ END $%x ] ---- \n" % stop_addr + + last_line_no = 0 + next_update = time.time() + 1 + stat_out = False + in_area = False + for line_no, line in enumerate(self.infile): + addr = int(line[:4], 16) + passed_addresses.add(addr) + + if in_area: + self.outfile.write(line) + stat_out = False + + if addr == stop_addr: + sys.stderr.flush() + self.outfile.flush() + + sys.stderr.write(end_seperator) + self.outfile.write(end_seperator) + + sys.stderr.flush() + self.outfile.flush() + in_area = False + continue + else: + if addr == start_addr: + sys.stderr.flush() + self.outfile.flush() + + sys.stderr.write(start_seperator) + self.outfile.write(start_seperator) + in_area = True + + self.outfile.write(line) + + sys.stderr.flush() + self.outfile.flush() + stat_out = False + continue + + if time.time() > next_update: + self.outfile.flush() + if stat_out: + sys.stderr.write("\r") + else: + sys.stderr.write("\n") + sys.stderr.write( + "process %i lines (%i/sec.), wait for $%x..." % ( + line_no, (line_no - last_line_no), start_addr, + ) + ) + passed_addresses -= all_addresses + if passed_addresses: + all_addresses.update(passed_addresses) + passed_addresses = ",".join(["$%x" % i for i in passed_addresses]) + sys.stderr.write( + "\nPassed unique addresses: %s\n" % passed_addresses + ) + passed_addresses = set() + else: + stat_out = True + + sys.stderr.flush() + last_line_no = line_no + next_update = time.time() + 1 + + self.outfile.close() def main(args): @@ -174,6 +254,10 @@ def main(args): xt.unique() return + if args.start_stop: + xt.start_stop(*args.start_stop) + return + if args.loop_filter: addr_stat = xt.load_tracefile(args.loop_filter) xt.filter(addr_filter=addr_stat) @@ -192,6 +276,14 @@ def main(args): xt.filter(addr_filter) +def start_stop_value(arg): + start_raw, stop_raw = arg.split("-") + start = int(start_raw.strip("$ "), 16) + stop = int(stop_raw.strip("$ "), 16) + sys.stderr.write("Use: $%x-$%x" % (start, stop)) + return (start, stop) + + def get_cli_args(): parser = argparse.ArgumentParser(description="Filter Xroar traces") parser.add_argument("infile", nargs="?", @@ -223,11 +315,20 @@ def get_cli_args(): nargs="?", help="Live Filter with given address file.", ) + + parser.add_argument("--start-stop", metavar="START-STOP", + type=start_stop_value, + nargs="?", + help="Enable trace only from $START to $STOP e.g.: --area=$4000-$5000", + ) + args = parser.parse_args() return args if __name__ == '__main__': +# sys.argv += ["--area=broken"] +# sys.argv += ["--area=1234-5678"] args = get_cli_args() main(args) From b0caa387ff8b239424ae22cc1e4dc14169506726 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Thu, 10 Jul 2014 17:44:04 +0200 Subject: [PATCH 114/151] update README --- misc/README.creole | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/misc/README.creole b/misc/README.creole index 556b149d..a4223da6 100644 --- a/misc/README.creole +++ b/misc/README.creole @@ -36,8 +36,9 @@ $ xroar -trace | python filter_xroar_trace.py --unique | tee startup_trace.txt }}} Let xroar start the machine e.g.: until the prompt is blicking. Then quit xroar. - -The **--unique** filtering skips all called addresses. So you get a small tracefile. + +The **--unique** will only collect a address one time. So "startup_trace.txt" is very small. +In other words: All trace lines are skip if the addresses was called in the past. Start again with the created **startup_trace.txt** file: @@ -47,6 +48,7 @@ $ xroar -trace | python filter_xroar_trace.py --loop-filter startup_trace.txt Now you will see only trace output for addresses that aren't in **startup_trace.txt** ;) + ==== Start / Stop filtering If you know the entry point in ROM and whant only see a area as a trace used this. @@ -54,6 +56,8 @@ e.g. See only traces after address **$8c37** was called and then until address * {{{ $ xroar -trace | python filter_xroar_trace.py --start-stop=8c37-91c1 | tee routine_trace.txt }}} +Note: You will not only see trace lines if address is between 8c37 and 91c1! +You can also do this: {{{--start-stop=9876-1234}}} So, it starts if $9876 is called and stops if $1234 is called. ==== Display statistics Just display the most often called addresses: From 7e63cd8f30e803972d9ab24f24568231d16c94cc Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Thu, 10 Jul 2014 17:44:12 +0200 Subject: [PATCH 115/151] WIP --- FloatingPoint/test_FPA0_02.bas | 62 ++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100755 FloatingPoint/test_FPA0_02.bas diff --git a/FloatingPoint/test_FPA0_02.bas b/FloatingPoint/test_FPA0_02.bas new file mode 100755 index 00000000..d9530bfc --- /dev/null +++ b/FloatingPoint/test_FPA0_02.bas @@ -0,0 +1,62 @@ +1 PRINT:PRINT "TEST FLOATS IN FPA0 V0.1" +2 PRINT "(GPL V3 OR ABOVE)" +3 PRINT:PRINT "COPYLEFT (C) 2014 JENS DIEMER":PRINT +11 COUNT=1'4 +20 LA=&H4000 ' LOAD / EXECUTE ADDRESS +25 PRINT "POKE MACHINE CODE TO: $";HEX$(LA) +30 PA = LA ' START ADDRESS FOR POKE +50 READ HB$ ' HEX CONSTANTS +60 IF HB$="END" THEN 100 +65 V=VAL("&H"+HB$) +70 POKE PA,V ' POKE VALUE INTO MEMORY +75 'PRINT "POKE $";HEX$(V);" AT $";HEX$(PA) +80 PA = PA + 1 ' INCREMENT POKE ADDRESS +90 GOTO 50 +100 PRINT "LOADED, END ADDRESS IS: $"; HEX$(PA-1) +110 PRINT:INPUT "INPUT START VALUE (DEZ)";D$ +115 IF D$="" THEN 20000 ELSE D=VAL(D$) +130 GOTO 500 +140 PRINT "UP/DOWN OR ANYKEY FOR NEW VALUE"; +150 I$ = INKEY$:IF I$="" THEN 150 +160 IF I$=CHR$(&H5E) THEN D=D2-(COUNT*2) : GOTO 500 ' UP KEYPRESS +170 IF I$=CHR$(&H0A) THEN D=D2 : GOTO 500 ' DOWN KEYPRESS +180 GOTO 110 ' NOT UP/DOWN +500 CLS':PRINT "D=";D +550 FOR I = 0 TO COUNT +551 D2=(D+I) 'AND &HFFFF +552 'PRINT "SET D=";D2 +553 POKE &H1052,D2/256 ' SET START VALUE +554 POKE &H1053,255 AND D2 ' SET START VALUE +559 'PRINT "EXEC MACHINE CODE" +560 EXEC LA +565 'PRINT "GET VALUES FROM FPA0" +570 EX =PEEK(&H704F) ' $4F FPA0 EXPONENT +580 MS =PEEK(&H7050) ' $50 FPA0 MS +580 NMS =PEEK(&H7051) ' %51 FPA0 NMS +580 NLS =PEEK(&H7052) ' $52 FPA0 NLS +580 LS =PEEK(&H7053) ' $53 FPA0 LS +580 SIGN=PEEK(&H7054) ' $54 FPA0 SIGN +690 PRINT "D=";RIGHT$(" "+STR$(D2),4);" FPA0=$"+HEX$(EX)+" $"+HEX$(MS)+" $"+HEX$(NMS)+" $"+HEX$(NLS)+" $"+HEX$(LS)+" $"+HEX$(SIGN) +700 NEXT I +710 GOTO 140 +800 ' MACHINE CODE IN HEX +810 ' LDD $1052 +820 DATA FC,10,52 +1030 ' JSR $8C37 ; ADD D TO FPA0 +1040 DATA BD,8C,37 +1050 ' LDX $4F +1060 DATA 9E,4F +1070 ' STX $704F +1080 DATA BF,70,4F +1090 ' LDX $51 +1100 DATA 9E,51 +1110 ' STX $7051 +1120 DATA BF,70,51 +1130 ' LDX $53 +1140 DATA 9E,53 +1150 ' STX $7053 +1160 DATA BF,70,53 +10000 ' RTS +10010 DATA 39 +10020 DATA END +20000 PRINT:PRINT "BYE" From cc875929d70d7ad8ad58ead95a94732ad02c4c03 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Sat, 12 Jul 2014 20:56:40 +0200 Subject: [PATCH 116/151] add "--add_cc" --- misc/README.creole | 28 +++++++++++++++++++++++++++- misc/add_info_in_trace.py | 33 ++++++++++++++++++++++++++++----- 2 files changed, 55 insertions(+), 6 deletions(-) mode change 100644 => 100755 misc/add_info_in_trace.py diff --git a/misc/README.creole b/misc/README.creole index a4223da6..d0994207 100644 --- a/misc/README.creole +++ b/misc/README.creole @@ -27,6 +27,7 @@ optional arguments: given count. }}} + === examples ==== Live filter @@ -178,6 +179,24 @@ Create file 'filtered_trace.txt'... Add address info on Xroar trace lines. Used informationfiles from: https://github.com/6809/rom-info +usage: +{{{ +$ ./add_info_in_trace.py --help +usage: add_info_in_trace.py [-h] [--infofile FILENAME] [--add_cc] + [infile] [outfile] + +Add info to Xroar traces + +positional arguments: + infile Xroar trace file or stdin + outfile If given: write output in a new file else: Display it. + +optional arguments: + -h, --help show this help message and exit + --infofile FILENAME ROM Info file from: https://github.com/6809/rom-info ;) + --add_cc Add CC info like '.F.IN..C' on every line. +}}} + e.g.: {{{ xroar -trace | python add_info_in_trace.py --infofile Dragon32.txt @@ -204,4 +223,11 @@ bb32| 80d0 SUBA #$d0 cc=a4 a=00 b=fd dp=00 x=02e2 y=804 bb34| 39 RTS cc=a4 a=00 b=fd dp=00 x=02e2 y=804b u=7fff s=7f2e | $bb34: $bb26-$bb34 - Jumped to from selfmodifying CHRGET routine at $009f 889b| 80ca SUBA #$ca cc=a1 a=36 b=fd dp=00 x=02e2 y=804b u=7fff s=7f2e | $889b: $8000-$9fff - CoCo - Extended Color BASIC ROM 889d| 2513 BCS $88b2 cc=a1 a=36 b=fd dp=00 x=02e2 y=804b u=7fff s=7f2e | $889d: $8000-$9fff - CoCo - Extended Color BASIC ROM -}}} \ No newline at end of file +}}} + +A trace with with **--add_cc** looks like: +{{{ +93da| 8e0040 LDX #$0040 cc=a0 a=00 b=05 dp=00 x=0040 y=b39b u=02e1 s=7f28| E.H..... | $93da: $8000-$9fff - CoCo - Extended Color BASIC ROM +93dd| 8c9e3b CMPX #$9e3b cc=a1 a=00 b=05 dp=00 x=0040 y=b39b u=02e1 s=7f28| E.H....C | $93dd: $8000-$9fff - CoCo - Extended Color BASIC ROM +93e0| 964f LDA <$4f cc=a5 a=00 b=05 dp=00 x=0040 y=b39b u=02e1 s=7f28| E.H..Z.C | $93e0: $8000-$9fff - CoCo - Extended Color BASIC ROM +}}} diff --git a/misc/add_info_in_trace.py b/misc/add_info_in_trace.py old mode 100644 new mode 100755 index 7083a47e..fbcb4ae9 --- a/misc/add_info_in_trace.py +++ b/misc/add_info_in_trace.py @@ -15,6 +15,21 @@ import sys import argparse + +def cc_value2txt(status): + """ + >>> cc_value2txt(0x50) + '.F.I....' + >>> cc_value2txt(0x54) + '.F.I.Z..' + >>> cc_value2txt(0x59) + '.F.IN..C' + """ + return "".join( + ["." if status & x == 0 else char for char, x in zip("EFHINZVC", (128, 64, 32, 16, 8, 4, 2, 1))] + ) + + class MemoryInfo(object): def __init__(self, rom_info_file): self.mem_info = self._get_rom_info(rom_info_file) @@ -93,9 +108,10 @@ def get_shortest(self, addr): class XroarTraceInfo(object): - def __init__(self, infile, outfile): + def __init__(self, infile, outfile, add_cc): self.infile = infile self.outfile = outfile + self.add_cc = add_cc def add_info(self, rom_info): last_line_no = 0 @@ -118,15 +134,19 @@ def add_info(self, rom_info): self.outfile.write(line) continue + line = line.strip() + if self.add_cc: + cc = int(line[49:51], 16) + cc_info = cc_value2txt(cc) + line += "| " + cc_info + addr_info = rom_info.get_shortest(addr) self.outfile.write( - "%s | %s\n" % (line.strip(), addr_info) + "%s | %s\n" % (line, addr_info) ) - - def main(args): - xt = XroarTraceInfo(args.infile, args.outfile) + xt = XroarTraceInfo(args.infile, args.outfile, args.add_cc) rom_info = MemoryInfo(args.infofile) xt.add_info(rom_info) @@ -147,6 +167,9 @@ def get_cli_args(): type=argparse.FileType("r"), help="ROM Info file from: https://github.com/6809/rom-info ;)", ) + parser.add_argument("--add_cc", action="store_true", + help="Add CC info like '.F.IN..C' on every line.", + ) args = parser.parse_args() return args From 0edd094f4aa71ffbd3108283b48b0bea973b22e7 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Sat, 26 Jul 2014 12:16:39 +0200 Subject: [PATCH 117/151] WIP: xroar GDB --- misc/xroar_gdb_tests.py | 98 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 misc/xroar_gdb_tests.py diff --git a/misc/xroar_gdb_tests.py b/misc/xroar_gdb_tests.py new file mode 100644 index 00000000..43fb9a1a --- /dev/null +++ b/misc/xroar_gdb_tests.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python +# encoding:utf-8 + +""" + :created: 2014 by Jens Diemer - www.jensdiemer.de + :license: GNU GPL v3 or above, see LICENSE for more details. +""" + +import subprocess +import os +import sys +import time +import socket +import threading + +GDB_IP="127.0.0.1" +GDB_PORT = 65520 + + +def start_xroar(xroar_args, cwd): + """ + http://www.6809.org.uk/xroar/doc/xroar.shtml#Debugging + """ + args = ["xroar", "-gdb", + "-gdb-ip", GDB_IP, + "-gdb-port", str(GDB_PORT), + ] + args += xroar_args + + sys.stderr.write( + "Start Xroar in %r with: '%s'\n" % ( + cwd, + " ".join([str(i) for i in args]) + ) + ) + xroar_process = subprocess.Popen(args=args, cwd=cwd) + return xroar_process + + +class XroarGDB(object): + """ + https://github.com/jedie/XRoar/blob/master/src/gdb.c + """ + def __init__(self): + sys.stderr.write("Connect to %s:%s ..." % (GDB_IP, GDB_PORT)) + self.s = socket.socket( + family=socket.AF_INET, +# family=socket.AF_UNSPEC, + type=socket.SOCK_STREAM, + proto=0 + ) + self.s.connect((GDB_IP, GDB_PORT)) + sys.stderr.write("connected.\n") + + self.running = True + self.print_recv_interval() + + def send(self, txt): + sys.stderr.write("Send %r ..." % txt) + self.s.sendall(txt) + sys.stderr.write("done.\n") + + def print_recv_interval(self): + print "recv: %s" % repr(self.s.recv(1024)) + if self.running: + t = threading.Timer(0.5, self.print_recv_interval) + t.deamon = True + t.start() + + +if __name__ == '__main__': + xroar_process = start_xroar( + xroar_args=[ + "-keymap", "de", + "-kbd-translate" + ], + cwd=os.path.expanduser("~/xroar") + ) + time.sleep(2) + + try: + xroar_gdb = XroarGDB() + xroar_gdb.send("g") + time.sleep(1) + xroar_gdb.send("p") + time.sleep(1) + xroar_gdb.send("g") + time.sleep(1) + finally: + print "tear down" + try: + xroar_gdb.running = False + xroar_gdb.s.close() + except: + pass + + time.sleep(1) + print " --- END --- " From 7bb61434a81f51e9062a0c0c83f1a84a9db08975 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Thu, 31 Jul 2014 16:40:31 +0200 Subject: [PATCH 118/151] add /InputOutput/keyboard.bas --- InputOutput/keyboard.bas | 32 ++++++++++++++++++++++++++++++++ README.creole | 7 +++++++ 2 files changed, 39 insertions(+) create mode 100755 InputOutput/keyboard.bas diff --git a/InputOutput/keyboard.bas b/InputOutput/keyboard.bas new file mode 100755 index 00000000..d46df35e --- /dev/null +++ b/InputOutput/keyboard.bas @@ -0,0 +1,32 @@ +1 CLS +2 PRINT "KEYBOARD INFO V0.1 (GPL V3+)" +3 PRINT "COPYLEFT (C) 2014 JENS DIEMER" +5 PRINT @ 64, "*** PRESS ANY KEY! ***" +6 PRINT @ 384, "------ LIVE FROM MEMORY: ------" +7 PRINT @ 416, "152 153 154 155 156 157 158 159" +8 GOSUB 1000 +10 C0=PEEK(&H0152) ' COLUMN 1'S BYTE +20 C1=PEEK(&H0153) ' COLUMN 2'S BYTE +30 C2=PEEK(&H0154) ' COLUMN 3'S BYTE +40 C3=PEEK(&H0155) ' COLUMN 4'S BYTE +50 C4=PEEK(&H0156) ' COLUMN 5'S BYTE +60 C5=PEEK(&H0157) ' COLUMN 6'S BYTE +70 C6=PEEK(&H0158) ' COLUMN 7'S BYTE +80 C7=PEEK(&H0159) ' COLUMN 8'S BYTE +90 I$=INKEY$ +110 IF I$="" THEN 150 +120 L$=I$ +130 H$=HEX$(ASC(I$)) +140 PRINT @ 64, "** LAST KEY: '"+ I$ +"' ASCII: $"+H$+" **" +150 PRINT @ 448, " "+HEX$(C0)+" "+HEX$(C1)+" "+HEX$(C2)+" "+HEX$(C3)+" "+HEX$(C4)+" "+HEX$(C5)+" "+HEX$(C6)+" "+HEX$(C7) +160 GOTO 10 +1000 PRINT @ 96, "------- DRAGON KEY MAP: -------" +1010 PRINT "PB0 PB1 PB2 PB3 PB4 PB5 PB6 PB7" +1020 PRINT " 0 1 2 3 4 5 6 7 " +1030 PRINT " 8 9 * ; , - . / " +1040 PRINT " @ A B C D E F G " +1050 PRINT " H I J K L M N O " +1060 PRINT " P Q R S T U V W " +1070 PRINT " X Y Z UP DWN LEF RIG SPC" +1080 PRINT "ENT CLR BRK SHIFT" +1090 RETURN diff --git a/README.creole b/README.creole index 5a171f73..7ad99d31 100755 --- a/README.creole +++ b/README.creole @@ -19,6 +19,13 @@ All script are copyleft 2013-2014 by Jens Diemer and license unter GNU GPL v3 or * Test CC Registers: ** https://github.com/jedie/PyDragon32/tree/master/TestCC_Registers +==== Input/Output Tests + +[[https://github.com/jedie/PyDragon32/tree/master/InputOutput/keyboard.bas|/InputOutput/keyboard.bas]] +Display memory Locations $0152 - $0159 (Keyboard matrix state table) + +{{{http://www.jensdiemer.de/static/jensdiemer.de/screenshots/keyboard01.png}}} +{{{http://www.jensdiemer.de/static/jensdiemer.de/screenshots/keyboard02.png}}} = donation From 1f89d919c3f0dbfeda87fff94581b941859fa7da Mon Sep 17 00:00:00 2001 From: Jens Diemer Date: Thu, 31 Jul 2014 16:43:14 +0200 Subject: [PATCH 119/151] Update README.creole --- README.creole | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.creole b/README.creole index 7ad99d31..adaab710 100755 --- a/README.creole +++ b/README.creole @@ -24,9 +24,8 @@ All script are copyleft 2013-2014 by Jens Diemer and license unter GNU GPL v3 or [[https://github.com/jedie/PyDragon32/tree/master/InputOutput/keyboard.bas|/InputOutput/keyboard.bas]] Display memory Locations $0152 - $0159 (Keyboard matrix state table) -{{{http://www.jensdiemer.de/static/jensdiemer.de/screenshots/keyboard01.png}}} -{{{http://www.jensdiemer.de/static/jensdiemer.de/screenshots/keyboard02.png}}} - +{{http://www.jensdiemer.de/static/jensdiemer.de/screenshots/keyboard01.png|KeyBoard Screenshot 01}} +{{http://www.jensdiemer.de/static/jensdiemer.de/screenshots/keyboard02.png|KeyBoard Screenshot 02}} = donation @@ -36,4 +35,4 @@ Display memory Locations $0152 - $0159 (Keyboard matrix state table) | Forum | [[http://forum.pylucid.org/]] | IRC | [[http://www.pylucid.org/permalink/304/irc-channel|#pylucid on freenode.net]] -| Github | [[http://github.com/jedie/PyLucid]] \ No newline at end of file +| Github | [[http://github.com/jedie/PyLucid]] From 860ac49e361f3aef58f40e5ccd588f1aefe3b94a Mon Sep 17 00:00:00 2001 From: Jens Diemer Date: Thu, 31 Jul 2014 16:44:50 +0200 Subject: [PATCH 120/151] Update README.creole --- README.creole | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.creole b/README.creole index adaab710..c1ed1022 100755 --- a/README.creole +++ b/README.creole @@ -25,6 +25,8 @@ All script are copyleft 2013-2014 by Jens Diemer and license unter GNU GPL v3 or Display memory Locations $0152 - $0159 (Keyboard matrix state table) {{http://www.jensdiemer.de/static/jensdiemer.de/screenshots/keyboard01.png|KeyBoard Screenshot 01}} +The "Y" key is pressed down. You see that this is saved in $0153. + {{http://www.jensdiemer.de/static/jensdiemer.de/screenshots/keyboard02.png|KeyBoard Screenshot 02}} = donation From bec817cf4ff9aa4f1489f89542f4118ab3947d63 Mon Sep 17 00:00:00 2001 From: Jens Diemer Date: Thu, 31 Jul 2014 16:46:20 +0200 Subject: [PATCH 121/151] Update README.creole --- README.creole | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.creole b/README.creole index c1ed1022..377b6e4a 100755 --- a/README.creole +++ b/README.creole @@ -24,9 +24,10 @@ All script are copyleft 2013-2014 by Jens Diemer and license unter GNU GPL v3 or [[https://github.com/jedie/PyDragon32/tree/master/InputOutput/keyboard.bas|/InputOutput/keyboard.bas]] Display memory Locations $0152 - $0159 (Keyboard matrix state table) +Example screenshow with the "Y" key is pressed down. You see that this is saved in $0153: {{http://www.jensdiemer.de/static/jensdiemer.de/screenshots/keyboard01.png|KeyBoard Screenshot 01}} -The "Y" key is pressed down. You see that this is saved in $0153. +Example with "U" is hold down: {{http://www.jensdiemer.de/static/jensdiemer.de/screenshots/keyboard02.png|KeyBoard Screenshot 02}} = donation From d9ad06437189703a06b2e5d841d11296fe582516 Mon Sep 17 00:00:00 2001 From: Jens Diemer Date: Thu, 31 Jul 2014 16:46:35 +0200 Subject: [PATCH 122/151] Update README.creole --- README.creole | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.creole b/README.creole index 377b6e4a..57dddd92 100755 --- a/README.creole +++ b/README.creole @@ -25,9 +25,11 @@ All script are copyleft 2013-2014 by Jens Diemer and license unter GNU GPL v3 or Display memory Locations $0152 - $0159 (Keyboard matrix state table) Example screenshow with the "Y" key is pressed down. You see that this is saved in $0153: + {{http://www.jensdiemer.de/static/jensdiemer.de/screenshots/keyboard01.png|KeyBoard Screenshot 01}} Example with "U" is hold down: + {{http://www.jensdiemer.de/static/jensdiemer.de/screenshots/keyboard02.png|KeyBoard Screenshot 02}} = donation From 7293c22bd3213451bfa908f16f84eee3f2db75bc Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Sun, 14 Sep 2014 22:31:43 +0200 Subject: [PATCH 123/151] Bugfix: Handle lines without CC e.g.: [RESET], [IRQ] --- misc/add_info_in_trace.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/misc/add_info_in_trace.py b/misc/add_info_in_trace.py index fbcb4ae9..14f17cda 100755 --- a/misc/add_info_in_trace.py +++ b/misc/add_info_in_trace.py @@ -136,9 +136,16 @@ def add_info(self, rom_info): line = line.strip() if self.add_cc: - cc = int(line[49:51], 16) - cc_info = cc_value2txt(cc) - line += "| " + cc_info + cc = line[49:51] + if cc: + try: + cc = int(cc, 16) + except ValueError as err: + msg = "ValueError: %s in line: %s" % (err, line) + line += "| %s" % msg + else: + cc_info = cc_value2txt(cc) + line += "| " + cc_info addr_info = rom_info.get_shortest(addr) self.outfile.write( From 7c9418fd5cf206220d429b55ba61298be7b4ff07 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Sun, 14 Sep 2014 22:32:02 +0200 Subject: [PATCH 124/151] ignore lines without address --- misc/filter_xroar_trace.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/misc/filter_xroar_trace.py b/misc/filter_xroar_trace.py index 7d7c1f2b..41c15fcc 100755 --- a/misc/filter_xroar_trace.py +++ b/misc/filter_xroar_trace.py @@ -184,7 +184,11 @@ def start_stop(self, start_addr, stop_addr): stat_out = False in_area = False for line_no, line in enumerate(self.infile): - addr = int(line[:4], 16) + try: + addr = int(line[:4], 16) + except ValueError: + continue + passed_addresses.add(addr) if in_area: From f0e4cf815234876a09dc6b591137e996c03f0360 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Thu, 25 Sep 2014 13:01:02 +0200 Subject: [PATCH 125/151] release as v0.3.0 --- README.creole | 4 ++++ dragonpy/__init__.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/README.creole b/README.creole index e4091316..6ab97cf5 100644 --- a/README.creole +++ b/README.creole @@ -357,6 +357,10 @@ Six is a Python 2 and 3 compatibility library. == History +* 25.09.2014 - Release v0.3.0: +** [[https://github.com/jedie/DragonPy/commit/f396551df730b509498d1b884cdda8f7075737c4|Change Display Queue to a simple Callback]] +** Reimplement [[https://github.com/jedie/DragonPy/commit/f3bfbdb2ae9906d8e051436173225c3fa8de1373|Multicomp 6809]] and [[https://github.com/jedie/DragonPy/commit/61c26911379d2b7ea6d07a8b479ab14c5d5a7154|SBC09]] +** Many code refactoring and cleanup * 14.09.2014 - Release v0.2.0 - Add a speedlimit, config dialog and IRQ: [[http://archive.worldofdragon.org/phpBB3/viewtopic.php?f=5&t=4308&p=11780#p11780|Forum post 11780]] * 05.09.2014 - Release v0.1.0 - Implement pause/resume, hard-/soft-reset 6809 in GUI and improve a little the GUI/Editor stuff: [[https://github.com/jedie/DragonPy/releases/tag/v0.1.0|v0.1.0]] see also: [[http://archive.worldofdragon.org/phpBB3/viewtopic.php?f=5&t=4308&p=11719#p11719|Forum post 11719]]. * 27.08.2014 - Run CoCo with Extended Color Basic v1.1, bugfix transfer BASIC Listing with [[https://github.com/jedie/DragonPy/compare/8fe24e5...697d39e|8fe24e5...697d39e]] see: [[http://archive.worldofdragon.org/phpBB3/viewtopic.php?f=5&t=4308&start=90#p11696|Forum post 11696]]. diff --git a/dragonpy/__init__.py b/dragonpy/__init__.py index d3ec452c..493f7415 100644 --- a/dragonpy/__init__.py +++ b/dragonpy/__init__.py @@ -1 +1 @@ -__version__ = "0.2.0" +__version__ = "0.3.0" From 6902b101391847c86af4a4b0643856385c817f04 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Mon, 29 Sep 2014 09:41:54 +0200 Subject: [PATCH 126/151] move BASIC programs --- {FloatingPoint => BASIC/FloatingPoint}/test_FPA0.bas | 0 {FloatingPoint => BASIC/FloatingPoint}/test_FPA0_02.bas | 0 {HexViewer => BASIC/HexViewer}/README.creole | 0 {HexViewer => BASIC/HexViewer}/hex_view01.bas | 0 {InputOutput => BASIC/InputOutput}/keyboard.bas | 0 {TestCC_Registers => BASIC/TestCC_Registers}/README.creole | 0 {TestCC_Registers => BASIC/TestCC_Registers}/testCC.bas | 0 .../TestCC_Registers}/testCC_ADDA.bas | 0 {TestCC_Registers => BASIC/TestCC_Registers}/testCC_DEC.bas | 0 {TestCC_Registers => BASIC/TestCC_Registers}/testCC_INC.bas | 0 {TestCC_Registers => BASIC/TestCC_Registers}/testCC_LSL.bas | 0 .../TestCC_Registers}/testCC_NEGA.bas | 0 .../TestCC_Registers}/testCC_SUBA.bas | 0 {TestCC_Registers => BASIC/TestCC_Registers}/testCC_TST.bas | 0 README.creole | 6 +++--- 15 files changed, 3 insertions(+), 3 deletions(-) rename {FloatingPoint => BASIC/FloatingPoint}/test_FPA0.bas (100%) rename {FloatingPoint => BASIC/FloatingPoint}/test_FPA0_02.bas (100%) rename {HexViewer => BASIC/HexViewer}/README.creole (100%) rename {HexViewer => BASIC/HexViewer}/hex_view01.bas (100%) rename {InputOutput => BASIC/InputOutput}/keyboard.bas (100%) rename {TestCC_Registers => BASIC/TestCC_Registers}/README.creole (100%) rename {TestCC_Registers => BASIC/TestCC_Registers}/testCC.bas (100%) rename {TestCC_Registers => BASIC/TestCC_Registers}/testCC_ADDA.bas (100%) rename {TestCC_Registers => BASIC/TestCC_Registers}/testCC_DEC.bas (100%) rename {TestCC_Registers => BASIC/TestCC_Registers}/testCC_INC.bas (100%) rename {TestCC_Registers => BASIC/TestCC_Registers}/testCC_LSL.bas (100%) rename {TestCC_Registers => BASIC/TestCC_Registers}/testCC_NEGA.bas (100%) rename {TestCC_Registers => BASIC/TestCC_Registers}/testCC_SUBA.bas (100%) rename {TestCC_Registers => BASIC/TestCC_Registers}/testCC_TST.bas (100%) diff --git a/FloatingPoint/test_FPA0.bas b/BASIC/FloatingPoint/test_FPA0.bas similarity index 100% rename from FloatingPoint/test_FPA0.bas rename to BASIC/FloatingPoint/test_FPA0.bas diff --git a/FloatingPoint/test_FPA0_02.bas b/BASIC/FloatingPoint/test_FPA0_02.bas similarity index 100% rename from FloatingPoint/test_FPA0_02.bas rename to BASIC/FloatingPoint/test_FPA0_02.bas diff --git a/HexViewer/README.creole b/BASIC/HexViewer/README.creole similarity index 100% rename from HexViewer/README.creole rename to BASIC/HexViewer/README.creole diff --git a/HexViewer/hex_view01.bas b/BASIC/HexViewer/hex_view01.bas similarity index 100% rename from HexViewer/hex_view01.bas rename to BASIC/HexViewer/hex_view01.bas diff --git a/InputOutput/keyboard.bas b/BASIC/InputOutput/keyboard.bas similarity index 100% rename from InputOutput/keyboard.bas rename to BASIC/InputOutput/keyboard.bas diff --git a/TestCC_Registers/README.creole b/BASIC/TestCC_Registers/README.creole similarity index 100% rename from TestCC_Registers/README.creole rename to BASIC/TestCC_Registers/README.creole diff --git a/TestCC_Registers/testCC.bas b/BASIC/TestCC_Registers/testCC.bas similarity index 100% rename from TestCC_Registers/testCC.bas rename to BASIC/TestCC_Registers/testCC.bas diff --git a/TestCC_Registers/testCC_ADDA.bas b/BASIC/TestCC_Registers/testCC_ADDA.bas similarity index 100% rename from TestCC_Registers/testCC_ADDA.bas rename to BASIC/TestCC_Registers/testCC_ADDA.bas diff --git a/TestCC_Registers/testCC_DEC.bas b/BASIC/TestCC_Registers/testCC_DEC.bas similarity index 100% rename from TestCC_Registers/testCC_DEC.bas rename to BASIC/TestCC_Registers/testCC_DEC.bas diff --git a/TestCC_Registers/testCC_INC.bas b/BASIC/TestCC_Registers/testCC_INC.bas similarity index 100% rename from TestCC_Registers/testCC_INC.bas rename to BASIC/TestCC_Registers/testCC_INC.bas diff --git a/TestCC_Registers/testCC_LSL.bas b/BASIC/TestCC_Registers/testCC_LSL.bas similarity index 100% rename from TestCC_Registers/testCC_LSL.bas rename to BASIC/TestCC_Registers/testCC_LSL.bas diff --git a/TestCC_Registers/testCC_NEGA.bas b/BASIC/TestCC_Registers/testCC_NEGA.bas similarity index 100% rename from TestCC_Registers/testCC_NEGA.bas rename to BASIC/TestCC_Registers/testCC_NEGA.bas diff --git a/TestCC_Registers/testCC_SUBA.bas b/BASIC/TestCC_Registers/testCC_SUBA.bas similarity index 100% rename from TestCC_Registers/testCC_SUBA.bas rename to BASIC/TestCC_Registers/testCC_SUBA.bas diff --git a/TestCC_Registers/testCC_TST.bas b/BASIC/TestCC_Registers/testCC_TST.bas similarity index 100% rename from TestCC_Registers/testCC_TST.bas rename to BASIC/TestCC_Registers/testCC_TST.bas diff --git a/README.creole b/README.creole index 1b7d36c7..86970dc7 100644 --- a/README.creole +++ b/README.creole @@ -298,14 +298,14 @@ All script are copyleft 2013-2014 by Jens Diemer and license unter GNU GPL v3 or === BASIC programms: * Simple memory HEX viewer: -** https://github.com/jedie/DragonPy/tree/master/HexViewer +** https://github.com/jedie/DragonPy/tree/master/BASIC/HexViewer * Test CC Registers: -** https://github.com/jedie/DragonPy/tree/master/TestCC_Registers +** https://github.com/jedie/DragonPy/tree/master/BASIC/TestCC_Registers ==== Input/Output Tests -[[https://github.com/jedie/DragonPy/tree/master/InputOutput/keyboard.bas|/InputOutput/keyboard.bas]] +[[https://github.com/jedie/DragonPy/tree/master/BASIC/InputOutput/keyboard.bas|/BASIC/InputOutput/keyboard.bas]] Display memory Locations $0152 - $0159 (Keyboard matrix state table) Example screenshow with the "Y" key is pressed down. You see that this is saved in $0153: From 8d4dd666106451ffe58df4a61f5abc5dfe19cf1f Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Mon, 29 Sep 2014 10:04:12 +0200 Subject: [PATCH 127/151] display more info on overflow error --- dragonpy/components/memory.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/dragonpy/components/memory.py b/dragonpy/components/memory.py index 3a431927..90b62b06 100644 --- a/dragonpy/components/memory.py +++ b/dragonpy/components/memory.py @@ -173,7 +173,14 @@ def load(self, address, data): ", ".join(["$%02x" % i for i in data]) ) for ea, datum in enumerate(data, address): - self._mem[ea] = datum + try: + self._mem[ea] = datum + except OverflowError, err: + msg="%s - ea=$%04x (load address was: $%04x - data length: %iBytes)" % ( + err, ea, address, len(data) + ) + raise OverflowError(msg) + # six.reraise(OverflowError, OverflowError(msg)) def load_file(self, romfile): data = romfile.get_data() From 394009836f730ebf83f349cf384d9763a270c878 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Mon, 29 Sep 2014 10:04:41 +0200 Subject: [PATCH 128/151] moved BASIC files --- {BASIC => BASIC tools}/FloatingPoint/test_FPA0.bas | 0 {BASIC => BASIC tools}/FloatingPoint/test_FPA0_02.bas | 0 {BASIC => BASIC tools}/HexViewer/README.creole | 0 {BASIC => BASIC tools}/HexViewer/hex_view01.bas | 0 {BASIC => BASIC tools}/InputOutput/keyboard.bas | 0 {BASIC => BASIC tools}/TestCC_Registers/README.creole | 0 {BASIC => BASIC tools}/TestCC_Registers/testCC.bas | 0 {BASIC => BASIC tools}/TestCC_Registers/testCC_ADDA.bas | 0 {BASIC => BASIC tools}/TestCC_Registers/testCC_DEC.bas | 0 {BASIC => BASIC tools}/TestCC_Registers/testCC_INC.bas | 0 {BASIC => BASIC tools}/TestCC_Registers/testCC_LSL.bas | 0 {BASIC => BASIC tools}/TestCC_Registers/testCC_NEGA.bas | 0 {BASIC => BASIC tools}/TestCC_Registers/testCC_SUBA.bas | 0 {BASIC => BASIC tools}/TestCC_Registers/testCC_TST.bas | 0 14 files changed, 0 insertions(+), 0 deletions(-) rename {BASIC => BASIC tools}/FloatingPoint/test_FPA0.bas (100%) rename {BASIC => BASIC tools}/FloatingPoint/test_FPA0_02.bas (100%) rename {BASIC => BASIC tools}/HexViewer/README.creole (100%) rename {BASIC => BASIC tools}/HexViewer/hex_view01.bas (100%) rename {BASIC => BASIC tools}/InputOutput/keyboard.bas (100%) rename {BASIC => BASIC tools}/TestCC_Registers/README.creole (100%) rename {BASIC => BASIC tools}/TestCC_Registers/testCC.bas (100%) rename {BASIC => BASIC tools}/TestCC_Registers/testCC_ADDA.bas (100%) rename {BASIC => BASIC tools}/TestCC_Registers/testCC_DEC.bas (100%) rename {BASIC => BASIC tools}/TestCC_Registers/testCC_INC.bas (100%) rename {BASIC => BASIC tools}/TestCC_Registers/testCC_LSL.bas (100%) rename {BASIC => BASIC tools}/TestCC_Registers/testCC_NEGA.bas (100%) rename {BASIC => BASIC tools}/TestCC_Registers/testCC_SUBA.bas (100%) rename {BASIC => BASIC tools}/TestCC_Registers/testCC_TST.bas (100%) diff --git a/BASIC/FloatingPoint/test_FPA0.bas b/BASIC tools/FloatingPoint/test_FPA0.bas similarity index 100% rename from BASIC/FloatingPoint/test_FPA0.bas rename to BASIC tools/FloatingPoint/test_FPA0.bas diff --git a/BASIC/FloatingPoint/test_FPA0_02.bas b/BASIC tools/FloatingPoint/test_FPA0_02.bas similarity index 100% rename from BASIC/FloatingPoint/test_FPA0_02.bas rename to BASIC tools/FloatingPoint/test_FPA0_02.bas diff --git a/BASIC/HexViewer/README.creole b/BASIC tools/HexViewer/README.creole similarity index 100% rename from BASIC/HexViewer/README.creole rename to BASIC tools/HexViewer/README.creole diff --git a/BASIC/HexViewer/hex_view01.bas b/BASIC tools/HexViewer/hex_view01.bas similarity index 100% rename from BASIC/HexViewer/hex_view01.bas rename to BASIC tools/HexViewer/hex_view01.bas diff --git a/BASIC/InputOutput/keyboard.bas b/BASIC tools/InputOutput/keyboard.bas similarity index 100% rename from BASIC/InputOutput/keyboard.bas rename to BASIC tools/InputOutput/keyboard.bas diff --git a/BASIC/TestCC_Registers/README.creole b/BASIC tools/TestCC_Registers/README.creole similarity index 100% rename from BASIC/TestCC_Registers/README.creole rename to BASIC tools/TestCC_Registers/README.creole diff --git a/BASIC/TestCC_Registers/testCC.bas b/BASIC tools/TestCC_Registers/testCC.bas similarity index 100% rename from BASIC/TestCC_Registers/testCC.bas rename to BASIC tools/TestCC_Registers/testCC.bas diff --git a/BASIC/TestCC_Registers/testCC_ADDA.bas b/BASIC tools/TestCC_Registers/testCC_ADDA.bas similarity index 100% rename from BASIC/TestCC_Registers/testCC_ADDA.bas rename to BASIC tools/TestCC_Registers/testCC_ADDA.bas diff --git a/BASIC/TestCC_Registers/testCC_DEC.bas b/BASIC tools/TestCC_Registers/testCC_DEC.bas similarity index 100% rename from BASIC/TestCC_Registers/testCC_DEC.bas rename to BASIC tools/TestCC_Registers/testCC_DEC.bas diff --git a/BASIC/TestCC_Registers/testCC_INC.bas b/BASIC tools/TestCC_Registers/testCC_INC.bas similarity index 100% rename from BASIC/TestCC_Registers/testCC_INC.bas rename to BASIC tools/TestCC_Registers/testCC_INC.bas diff --git a/BASIC/TestCC_Registers/testCC_LSL.bas b/BASIC tools/TestCC_Registers/testCC_LSL.bas similarity index 100% rename from BASIC/TestCC_Registers/testCC_LSL.bas rename to BASIC tools/TestCC_Registers/testCC_LSL.bas diff --git a/BASIC/TestCC_Registers/testCC_NEGA.bas b/BASIC tools/TestCC_Registers/testCC_NEGA.bas similarity index 100% rename from BASIC/TestCC_Registers/testCC_NEGA.bas rename to BASIC tools/TestCC_Registers/testCC_NEGA.bas diff --git a/BASIC/TestCC_Registers/testCC_SUBA.bas b/BASIC tools/TestCC_Registers/testCC_SUBA.bas similarity index 100% rename from BASIC/TestCC_Registers/testCC_SUBA.bas rename to BASIC tools/TestCC_Registers/testCC_SUBA.bas diff --git a/BASIC/TestCC_Registers/testCC_TST.bas b/BASIC tools/TestCC_Registers/testCC_TST.bas similarity index 100% rename from BASIC/TestCC_Registers/testCC_TST.bas rename to BASIC tools/TestCC_Registers/testCC_TST.bas From 9ab6a04ba64e231fee15c08a03e176f53de0011d Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Mon, 29 Sep 2014 10:04:54 +0200 Subject: [PATCH 129/151] don't open settings dialog --- dragonpy/core/gui.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dragonpy/core/gui.py b/dragonpy/core/gui.py index f44e2bcc..7808f9bc 100644 --- a/dragonpy/core/gui.py +++ b/dragonpy/core/gui.py @@ -107,7 +107,8 @@ def __init__(self, cfg, user_input_queue): self.config_window = None self.menubar.add_command(label="config", command=self.command_config) - self.root.after(200, self.command_config) # FIXME: Only for developing: Open config on startup! + # FIXME: Only for developing: Open config on startup! + # self.root.after(200, self.command_config) # help menu helpmenu = tkinter.Menu(self.menubar, tearoff=0) From 8da46f01462431400c0c9751786bd0eaace45141 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Mon, 29 Sep 2014 10:09:57 +0200 Subject: [PATCH 130/151] add origin INVADER.bas from Jim Gerrie: .cas version from http://www3.ns.sympatico.ca/jimgerrie/Dragon_JimGerrie.zip see: http://archive.worldofdragon.org/phpBB3/viewtopic.php?f=7&t=4760&p=11886#p11871 --- BASIC games/INVADER.bas | 93 +++++++++++++++++++++++++++++++++++++++++ basic_editor/editor.py | 6 +-- 2 files changed, 95 insertions(+), 4 deletions(-) create mode 100755 BASIC games/INVADER.bas diff --git a/BASIC games/INVADER.bas b/BASIC games/INVADER.bas new file mode 100755 index 00000000..13abc96a --- /dev/null +++ b/BASIC games/INVADER.bas @@ -0,0 +1,93 @@ +0 GOSUB10005:GOSUB10000:CLS:CLEAR5000:DIML(511),Y(542),T(542),K(255),L$,B$,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,AK,BC,N1,LV,SC:GOTO900 +1 RETURN +2 FORU=I TO B:RESET(Y,K):K=K-I:ONPOINT(Y,K)GOTO5,5,5,3,3,3,3,27:SET(Y,K,I):NEXT:RESET(Y,K):RETURN +3 H=INT(K/W)*G+INT(Y/W):PRINT@H-I,CHR$(N2)CHR$(N2+RND(N7))CHR$(N2+RND(N7));:AK=AK+I:IFAK>N1 THENU=B:NEXT:L=E:RETURN +4 VP=VARPTR(L$):FORZ=-I TOI:POKE(PEEK(VP+W)*V)+PEEK(VP+R)+(H-L)+Z,N2:NEXT:SET(Y,K,I):U=B:K=O:NEXT:RETURN +5 RESET(Y,K):U=B:K=O:Y=O:NEXT:RESET(Y,K):RETURN +6 RESET(C,M):M=M+W:ONPOINT(C,M)GOTO24,14,14:SET(C,M,I):RETURN +7 T=R:NEXT:OND+I GOTO8,8:S=L:L=E:NEXT:D=I:F=O:E=N9:X=S1:GOTO9 +8 S=L:L=E:NEXT:D=-I:F=N1:E=N1:X=R:MS=MS+W:PRINT@MS,M$;:IFMS>N8 THENPRINT@MS,LEFT$(B$,5);:MS=O +9 BC=BC+I:IFBC>(G-AK)/W AND SB)+I GOTO1:P=P-W:Q=Q-I:RETURN +16 ON(PO AND AK=32 THEN160 +100 PRINT@MS,LEFT$(B$,6);:IFS<448 THEN FORL=S TO 479 STEP32:PRINT@L-N1,LEFT$(B$,L(L-N1));:PRINT@L,LEFT$(L$,L(L));:SOUND1,2:NEXT +105 PRINT@0, "YOU ARE DEAD. ";:SC=SC+(AK*LV):PRINT"SCORE=";SC:IFSC>HS THEN HS=SC +110 PRINT@32,"PLAY AGAIN (Y/N)? ";:PRINT"HIGH=";HS +115 IFAK<32 THEN PRINT@196,"THE ALIENS HAVE LANDED!";:SOUND100,15 +120 I$=INKEY$:IFI$=""THEN120 +130 IFI$="Y" THEN 1570 +140 IFI$="N"THENGOSUB10010:GOSUB10003:END +150 GOTO120 +160 PRINT@0,"ANOTHER WAVE IS COMING!" +170 SOUND100,3:SOUND50,3:SOUND100,3:SOUND50,5:SOUND50,5:SOUND100,3:SOUND50,3 +180 SC=SC+(32*LV):PRINT@266,"SCORE=";SC;:FORT=1TO2000:NEXT +185 IFNP=SS THEN PRINT@198,"+100 SURVIVAL BONUS";:FORT=1 TO 2:FORZ=1 TO 24:READAA,BB:SOUNDAA,BB:NEXT:RESTORE:NEXT:SC=SC+100 +190 XM=XM+I:IFXM=2 THEN GOSUB200 +195 GOSUB1600:GOTO10 +200 XM=0:IFNP=3 THEN 320 +300 PRINT@198,"*SHIELDS REPAIRED!*"; +310 NP=NP+1 +315 FORT=1 TO 10:POKE1535,48+NP+64:PRINT@Q,Q$;:FORZ=1 TO 200:NEXT:POKE1535,48+NP:PRINT@Q,P$;:FORZ=1 TO 200:NEXT:NEXT +320 RETURN +900 PRINT@231,"* SPACE INVADERS *"; +910 PRINT@291,"FOR THE MC-10 BY JIM GERRIE"; +920 PRINT@485,"PRESS ANY KEY TO BEGIN"; +930 P$=INKEY$:R=RND(1000):IFP$="" THEN 930 +999 CLS:PRINT"PLEASE WAIT..." +1000 FORT=1TO32:B$=B$+CHR$(128):NEXT +1010 O=0:I=1:W=2:R=3:B=4:G=32:N=29:V=256:N1=31 +1020 K(65)=1:K(83)=2:K(32)=3:K(8)=1:K(9)=2:A=17023 +1030 DIMN2,N3,N4,N5,N6,N7,N8,N9,MS,NS,XM,NP,SS,Q$,P$,I$,M$,V(3),VP,S1 +1040 P$=CHR$(128)+CHR$(135+32)+CHR$(139+32)+CHR$(128):Q$=CHR$(128)+CHR$(135+16)+CHR$(139+16)+CHR$(128) +1050 N2=128:N3=27:N4=58:N5=28:N6=480:N7=14:N8=24:N9=479:S1=61:SH$=CHR$(135+32)+CHR$(139+32) +1500 FORT=0TO256:L(T)=224:NEXT:FORT=257TO479:L(T)=L(T-1)-1:NEXT +1510 FORT=0TO286:T(T)=3:Y(T)=INT((T/16)+.1):NEXT +1520 FORT=287TO350:T(T)=2:Y(T)=INT((T/16)+.1):NEXT +1530 FORT=351TO414:T(T)=1:Y(T)=INT((T/16)+.1):NEXT +1540 FORT=415TO478:T(T)=0:Y(T)=INT((T/16)+.1):NEXT +1550 FORT=479TO542:T(T)=0:Y(T)=28:NEXT +1560 FORT=0TO3:V(T)=B*T:NEXT:M$=CHR$(128)+CHR$(128)+CHR$(241)+CHR$(255)+CHR$(251) +1570 SC=0:LV=0:NS=0:NP=3:MS=0:XM=0:GOSUB1600 +1580 GOTO10 +1600 CLS0:D=1:E=479:X=61:V(0)=0:NS=NS+1:LV=LV+1:IFLV>4 THEN LV=4 +1605 PRINT@480,NS;" ";:FORT=1504TO1508:POKET,PEEK(T)-64:NEXT:FORT=1509TO1535:POKET,G:NEXT:POKE1535,48+NP +1610 FORT=1TO3:PRINT@348+(9*T),CHR$(135);CHR$(143);CHR$(139);:PRINT@348+(9*T)+32,CHR$(143);CHR$(143);CHR$(143);:NEXT +1630 P=31:Q=448+14:M=1:L=LV*32:S=L:AK=0:BC=0:F=0:H=0:K=0:U=0:Z=0 +1700 L$="":C=32:FORY=1TO4:C=C+16:FORT=1TO8:L$=L$+CHR$(128+C)+CHR$(133+C)+CHR$(141+C):NEXT:IFY=4 THEN L$=L$+LEFT$(B$,W):GOTO1715 +1712 L$=L$+B$+LEFT$(B$,8) +1715 NEXT:C=0:SS=NP:RETURN +2100 DATA 89,1,125,1,147,1,176,1,147,1,125,1 +2110 DATA 89,1,125,1,147,1,89,1,147,1,125,1 +2120 DATA 89,1,125,1,147,1,176,1,147,1,125,1 +2130 DATA 89,1,125,1,147,1,89,1,147,1,125,1 +10000 IF PEEK(65535)=27 THEN POKE65497,0:GOTO10002 :ELSE CLS:INPUT"CAN YOUR COMPUTER HANDLE DOUBLE SPEED (Y/N)";A$ +10001 IF A$="Y" THEN POKE65495,0 :ELSE IF A$<>"N" THEN10000 +10002 CLS:RETURN +10003 IF PEEK(65535)=27 THEN POKE65496,0 :ELSE POKE65494,0 +10004 RETURN +10005 :' ENABLE DRAGON SPEEDKEY +10006 IF PEEK(65535)<>180 THEN 10008 +10007 IF PEEK(269)+PEEK(270)<>1 THEN POKE65283,52:POKE256,116:POKE257,1:POKE258,81:POKE259,126:POKE260,PEEK(269):POKE 261,PEEK(270):POKE269,1:POKE270,0:POKE65283,53 +10008 RETURN +10009 :' DISABLE DRAGON SPEEDKEY +10010 IF PEEK(65535)<>180 THEN 10012 +10011 IF PEEK(269)+PEEK(270)=1 THEN POKE65283,52:POKE269,PEEK(260):POKE270,PEEK(261):POKE65283,53 +10012 RETURN diff --git a/basic_editor/editor.py b/basic_editor/editor.py index f9f0ebef..dc4f8b88 100644 --- a/basic_editor/editor.py +++ b/basic_editor/editor.py @@ -364,11 +364,9 @@ def test(): from dragonpy.Dragon32.config import Dragon32Cfg cfg = Dragon32Cfg(CFG_DICT) - - filepath = os.path.join(os.path.abspath(os.path.dirname(__file__)), - "..", "BASIC examples", - "hex_view01.bas" + # "..", "BASIC examples", "hex_view01.bas" + "..", "BASIC games", "INVADER.bas" ) with open(filepath, "r") as f: From af8f074d1070dad08b8b7c5384dc80488f196e6e Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Mon, 29 Sep 2014 11:10:53 +0200 Subject: [PATCH 131/151] Add vertical scrollbar to basic editor and disable wrap --- basic_editor/editor.py | 44 ++++++----------- basic_editor/scrolled_text.py | 92 +++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 28 deletions(-) create mode 100644 basic_editor/scrolled_text.py diff --git a/basic_editor/editor.py b/basic_editor/editor.py index dc4f8b88..19602169 100644 --- a/basic_editor/editor.py +++ b/basic_editor/editor.py @@ -19,33 +19,33 @@ import string import sys +from basic_editor.scrolled_text import ScrolledText import dragonlib from basic_editor.editor_base import BaseExtension from basic_editor.highlighting import TkTextHighlighting from dragonlib.utils.auto_shift import invert_shift from dragonlib.utils.logging_utils import pformat_program_dump -log = logging.getLogger(__name__) +log = logging.getLogger(__name__) try: # Python 3 import tkinter from tkinter import filedialog from tkinter import messagebox - from tkinter import scrolledtext except ImportError: # Python 2 import Tkinter as tkinter import tkFileDialog as filedialog import tkMessageBox as messagebox - import ScrolledText as scrolledtext class MultiStatusBar(tkinter.Frame): """ code from idlelib.MultiStatusBar.MultiStatusBar """ + def __init__(self, master, **kw): tkinter.Frame.__init__(self, master, **kw) self.labels = {} @@ -63,6 +63,7 @@ def set_label(self, name, text='', side=tkinter.LEFT): class TkTextHighlightCurrentLine(BaseExtension): after_id = None TAG_CURRENT_LINE = "current_line" + def __init__(self, editor): super(TkTextHighlightCurrentLine, self).__init__(editor) @@ -92,22 +93,8 @@ def __update_interval(self): self.after_id = self.text.after(10, self.__update_interval) -class ScrolledText2(scrolledtext.ScrolledText): - def save_position(self): - # save text cursor position: - self.old_text_pos = self.index(tkinter.INSERT) - # save scroll position: - self.old_first, self.old_last = self.yview() - - def restore_position(self): - # restore text cursor position: - self.mark_set(tkinter.INSERT, self.old_text_pos) - # restore scroll position: - self.yview_moveto(self.old_first) - - class EditorWindow(object): - FILETYPES = [ # For filedialog + FILETYPES = [# For filedialog ("BASIC Listings", "*.bas", "TEXT"), ("Text files", "*.txt", "TEXT"), ("All files", "*"), @@ -142,7 +129,7 @@ def __init__(self, cfg, gui=None): self.base_title = "%s - BASIC Editor" % self.cfg.MACHINE_NAME self.root.title(self.base_title) - self.text = ScrolledText2( + self.text = ScrolledText( master=self.root, height=30, width=80 ) self.text.config( @@ -232,15 +219,15 @@ def event_text_key(self, event): self.text.insert(tkinter.INSERT, converted_char) # Insert converted char return "break" -# def event_syntax_check(self, event): -# index = self.text.search(r'\s', "insert", backwards=True, regexp=True) -# if index == "": -# index ="1.0" -# else: -# index = self.text.index("%s+1c" % index) -# word = self.text.get(index, "insert") -# log.critical("inserted word: %r", word) -# print self.machine_api.parse_ascii_listing(word) + # def event_syntax_check(self, event): + # index = self.text.search(r'\s', "insert", backwards=True, regexp=True) + # if index == "": + # index ="1.0" + # else: + # index = self.text.index("%s+1c" % index) + # word = self.text.get(index, "insert") + # log.critical("inserted word: %r", word) + # print self.machine_api.parse_ascii_listing(word) def setup_filepath(self, filepath): log.critical(filepath) @@ -362,6 +349,7 @@ def test(): "use_bus": False, } from dragonpy.Dragon32.config import Dragon32Cfg + cfg = Dragon32Cfg(CFG_DICT) filepath = os.path.join(os.path.abspath(os.path.dirname(__file__)), diff --git a/basic_editor/scrolled_text.py b/basic_editor/scrolled_text.py new file mode 100644 index 00000000..cd86251a --- /dev/null +++ b/basic_editor/scrolled_text.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python +# encoding:utf8 + +""" + Tkinter ScrolledText widget with horizontal and vertical scroll bars. + + :created: 2014 by Jens Diemer - www.jensdiemer.de + :copyleft: 2014 by the DragonPy team, see AUTHORS for more details. + :license: GNU GPL v3 or above, see LICENSE for more details. +""" + +try: + # Python 3 + import tkinter + from tkinter import filedialog + from tkinter import messagebox + from tkinter import scrolledtext +except ImportError: + # Python 2 + import Tkinter as tkinter + import tkFileDialog as filedialog + import tkMessageBox as messagebox + import ScrolledText as scrolledtext + + +class ScrolledText(tkinter.Text): + def __init__(self, master=None, **kw): + frame = tkinter.Frame(master) + frame.rowconfigure(0, weight = 1) + frame.columnconfigure(0, weight = 1) + + xscrollbar = tkinter.Scrollbar(frame, orient=tkinter.HORIZONTAL) + yscrollbar = tkinter.Scrollbar(frame, orient=tkinter.VERTICAL) + + frame.grid(row=0, column=0, sticky=tkinter.NSEW) + xscrollbar.grid(row=1, column=0, sticky=tkinter.EW) + yscrollbar.grid(row=0, column=1, sticky=tkinter.NS) + + _defaults_options={'wrap': tkinter.NONE} + options = _defaults_options.copy() + options.update(kw) + options.update({'yscrollcommand': yscrollbar.set}) + options.update({'xscrollcommand': xscrollbar.set}) + + tkinter.Text.__init__(self, frame, **options) + + self.grid(row=0, column=0, sticky=tkinter.NSEW) + + xscrollbar.config(command=self.xview) + yscrollbar.config(command=self.yview) + + + def __str__(self): + return str(self.frame) + + def save_position(self): + """ + save cursor and scroll position + """ + # save text cursor position: + self.old_text_pos = self.index(tkinter.INSERT) + # save scroll position: + self.old_first, self.old_last = self.yview() + + def restore_position(self): + """ + restore cursor and scroll position + """ + # restore text cursor position: + self.mark_set(tkinter.INSERT, self.old_text_pos) + # restore scroll position: + self.yview_moveto(self.old_first) + + +def example(): + import __main__ + + root = tkinter.Tk() + + text = ScrolledText(master=root, bg='white', height=20) + text.insert(tkinter.END, "X"*150) + text.insert(tkinter.END, __main__.__doc__) + text.insert(tkinter.END, "X"*150) + text.focus_set() + text.grid(row=0, column=0, sticky=tkinter.NSEW) + + root.columnconfigure(0, weight=1) + root.rowconfigure(0, weight=1) + root.mainloop() + +if __name__ == "__main__": + example() From f7a9bf3786e67692e2bd76c9700f4663786d7c0d Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Tue, 30 Sep 2014 08:30:56 +0200 Subject: [PATCH 132/151] Display also datum --- dragonpy/components/memory.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/dragonpy/components/memory.py b/dragonpy/components/memory.py index 90b62b06..566ca40c 100644 --- a/dragonpy/components/memory.py +++ b/dragonpy/components/memory.py @@ -176,11 +176,10 @@ def load(self, address, data): try: self._mem[ea] = datum except OverflowError, err: - msg="%s - ea=$%04x (load address was: $%04x - data length: %iBytes)" % ( - err, ea, address, len(data) + msg="%s - datum=$%x ea=$%04x (load address was: $%04x - data length: %iBytes)" % ( + err, datum, ea, address, len(data) ) raise OverflowError(msg) - # six.reraise(OverflowError, OverflowError(msg)) def load_file(self, romfile): data = romfile.get_data() From d6a25ce122c3db24005ea82762d348453e7c0c9d Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Tue, 30 Sep 2014 09:46:57 +0200 Subject: [PATCH 133/151] Add a more informative "display tokens" window --- basic_editor/editor.py | 120 +++++++++++++++++++++++++++++++++++++++- dragonlib/core/basic.py | 26 +++++---- 2 files changed, 133 insertions(+), 13 deletions(-) diff --git a/basic_editor/editor.py b/basic_editor/editor.py index 19602169..908bcfb3 100644 --- a/basic_editor/editor.py +++ b/basic_editor/editor.py @@ -93,6 +93,109 @@ def __update_interval(self): self.after_id = self.text.after(10, self.__update_interval) +class TokenWindow(object): + def __init__(self, cfg, master): + self.cfg = cfg + self.machine_api = self.cfg.machine_api + + self.root = tkinter.Toplevel(master) + self.root.geometry("+%d+%d" % ( + master.winfo_rootx() + master.winfo_width(), + master.winfo_y() # FIXME: Different on linux. + )) + self.root.columnconfigure(0, weight=1) + self.root.rowconfigure(0, weight=1) + self.base_title = "%s - Tokens" % self.cfg.MACHINE_NAME + self.root.title(self.base_title) + + self.text = ScrolledText( + master=self.root, height=30, width=80 + ) + self.text.config( + background="#ffffff", foreground="#000000", + highlightthickness=0, + font=('courier', 11), + ) + self.text.grid(row=0, column=0, sticky=tkinter.NSEW) + + self.set_status_bar() # Create widget, add bindings and after_idle() update + + self.text.after_idle(self.set_token_info) + + def display_listing(self, content): + program_dump = self.machine_api.ascii_listing2program_dump(content) + formated_dump = pformat_program_dump(program_dump) + + self.text.insert(tkinter.END, formated_dump) + + self.text.bind("", self.on_mouse_move) + + def on_mouse_move(self, event): + index = self.text.index("@%s,%s" % (event.x, event.y)) + + try: + word = self.text.get("%s wordstart" % index, "%s wordend" % index) + except tkinter.TclError as err: + log.critical("TclError: %s", err) + return + + try: + token_value = int(word, 16) + except ValueError: + return + + log.critical("$%x", token_value) + basic_word = self.machine_api.token_util.token2ascii(token_value) + + info = "%s $%02x == %r" % (index, token_value, basic_word) + + try: + selection_index="%s-%s" % (self.text.index("sel.first"), self.text.index("sel.last")) + selection=self.text.selection_get() + except tkinter.TclError: + # no selection + pass + else: + log.critical(" selection: %s: %r", selection_index, selection) + + selection = selection.replace("$", "") + token_values = [int(part, 16) for part in selection.split() if part.strip()] + log.critical("values: %r", token_values) + basic_selection = self.machine_api.token_util.tokens2ascii(token_values) + + info += " - selection: %r" % basic_selection + + self.status_bar.set_label("cursor_info", info) + + ########################################################################### + # Status bar + + def set_status_bar(self): + self.status_bar = MultiStatusBar(self.root) + if sys.platform == "darwin": + # Insert some padding to avoid obscuring some of the statusbar + # by the resize widget. + self.status_bar.set_label('_padding1', ' ', side=tkinter.RIGHT) + self.status_bar.grid(row=1, column=0) + + self.text.bind("<>", self.set_line_and_column) + self.text.event_add("<>", + "", "") + self.text.after_idle(self.set_line_and_column) + + def set_line_and_column(self, event=None): + line, column = self.text.index(tkinter.INSERT).split('.') + self.status_bar.set_label('column', 'Column: %s' % column) + self.status_bar.set_label('line', 'Line: %s' % line) + + ########################################################################### + + def set_token_info(self, event=None): + line, column = self.text.index(tkinter.INSERT).split('.') + + + + class EditorWindow(object): FILETYPES = [# For filedialog ("BASIC Listings", "*.bas", "TEXT"), @@ -270,9 +373,8 @@ def command_save_file(self): def debug_display_tokens(self): content = self.get_content() - program_dump = self.machine_api.ascii_listing2program_dump(content) - msg = pformat_program_dump(program_dump) - messagebox.showinfo("Program Dump:", msg, parent=self.root) + self.token_window = TokenWindow(self.cfg, self.root) + self.token_window.display_listing(content) def renumber_listing(self): # save text cursor and scroll position @@ -337,6 +439,17 @@ def run_basic_editor(cfg, default_content=None): def test(): + from dragonlib.utils.logging_utils import setup_logging + + setup_logging( +# level=1 # hardcore debug ;) +# level=10 # DEBUG +# level=20 # INFO +# level=30 # WARNING +# level=40 # ERROR + level=50 # CRITICAL/FATAL + ) + CFG_DICT = { "verbosity": None, "display_cycle": False, @@ -364,4 +477,5 @@ def test(): if __name__ == "__main__": + test() diff --git a/dragonlib/core/basic.py b/dragonlib/core/basic.py index ea3ad506..8c60d8b0 100644 --- a/dragonlib/core/basic.py +++ b/dragonlib/core/basic.py @@ -54,6 +54,20 @@ def token2ascii(self, value): else: return result + def tokens2ascii(self, values): + line="" + old_value = None + for value in values: + if value == 0xff: + old_value = value + continue + if old_value is not None: + value = (old_value << 8) + value + old_value = None + code = self.token2ascii(value) + line += code + return line + def chars2tokens(self, chars): return [ord(char) for char in chars] @@ -197,16 +211,8 @@ def get_content(self, code=None): code = self.line_code line = "%i " % self.line_number - old_value = None - for value in code: - if value == 0xff: - old_value = value - continue - if old_value is not None: - value = (old_value << 8) + value - old_value = None - code = self.token_util.token2ascii(value) - line += code + line += self.token_util.tokens2ascii(code) + return line def log_line(self): From 160e17e29969e76c91268258d7c2e3658f505846 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Tue, 30 Sep 2014 10:58:37 +0200 Subject: [PATCH 134/151] Bugfix if line number > $ff see: http://archive.worldofdragon.org/phpBB3/viewtopic.php?f=5&t=4308&p=11890#p11890 --- dragonlib/core/basic.py | 7 +++- dragonlib/tests/test_api.py | 58 ++++++++++++++++++++++++++-- dragonlib/tests/test_basic_parser.py | 3 +- 3 files changed, 62 insertions(+), 6 deletions(-) diff --git a/dragonlib/core/basic.py b/dragonlib/core/basic.py index 8c60d8b0..84ff3f8d 100644 --- a/dragonlib/core/basic.py +++ b/dragonlib/core/basic.py @@ -204,7 +204,10 @@ def code_objects_load(self, line_number, code_objects): self.line_code = self.token_util.code_objects2token(code_objects) def get_tokens(self): - return [self.line_number] + self.line_code + """ + return two bytes line number + the code + """ + return list(word2bytes(self.line_number)) + self.line_code def get_content(self, code=None): if code is None: # start @@ -266,7 +269,7 @@ def basic_lines2program_dump(self, basic_lines, program_start): count = len(basic_lines) for no, line in enumerate(basic_lines, 1): line.log_line() - line_tokens = [0x00] + line.get_tokens() + [0x00] + line_tokens = line.get_tokens() + [0x00] current_address += len(line_tokens) + 2 program_dump += word2bytes(current_address) diff --git a/dragonlib/tests/test_api.py b/dragonlib/tests/test_api.py index 73051ecb..a3455093 100644 --- a/dragonlib/tests/test_api.py +++ b/dragonlib/tests/test_api.py @@ -145,7 +145,7 @@ def test_ascii2tokens01(self): ) tokens = basic_lines[0].get_tokens() self.assertHexList(tokens, [ - 0x0a, # 10 + 0x00, 0x0a, # 10 0xa0, # CLS ]) self.assertEqual(len(basic_lines), 1) @@ -156,7 +156,7 @@ def test_ascii2tokens02(self): ) tokens = basic_lines[0].get_tokens() self.assertHexList(tokens, [ - 0x32, # 50 + 0x00, 0x32, # 50 # I$ = INKEY$:IF I$="" THEN 50 0x49, 0x24, 0x20, 0xcb, 0x20, 0xff, 0x9a, 0x3a, 0x85, 0x20, 0x49, 0x24, 0xcb, 0x22, 0x22, 0x20, 0xbf, 0x20, 0x35, 0x30, ]) @@ -358,6 +358,57 @@ def test_auto_add_colon_before_else(self): ) self.assertDump2Listing(ascii_listing,program_dump) + def test_two_byte_line_numbers(self): + """ + Every line number is saved as one word! + """ + ascii_listing = self._prepare_text(""" + 254 PRINT "A" + 255 PRINT "B" + 256 PRINT "C" + 257 PRINT "D" + """) + program_dump = ( + # program start address: $1e01 + 0x1e, 0x0b, # -> next address (length: 10) + 0x00, 0xfe, # -> 254 (line number) + 0x87, # -> 'PRINT' + 0x20, # -> ' ' + 0x22, # -> '"' + 0x41, # -> 'A' + 0x22, # -> '"' + 0x00, # -> '\x00' + 0x1e, 0x15, # -> next address (length: 10) + 0x00, 0xff, # -> 255 (line number) + 0x87, # -> 'PRINT' + 0x20, # -> ' ' + 0x22, # -> '"' + 0x42, # -> 'B' + 0x22, # -> '"' + 0x00, # -> '\x00' + 0x1e, 0x1f, # -> next address (length: 10) + 0x01, 0x00, # -> 256 (line number) + 0x87, # -> 'PRINT' + 0x20, # -> ' ' + 0x22, # -> '"' + 0x43, # -> 'C' + 0x22, # -> '"' + 0x00, # -> '\x00' + 0x1e, 0x29, # -> next address (length: 10) + 0x01, 0x01, # -> 257 (line number) + 0x87, # -> 'PRINT' + 0x20, # -> ' ' + 0x22, # -> '"' + 0x44, # -> 'D' + 0x22, # -> '"' + 0x00, # -> '\x00' + 0x00, 0x00, # -> end address + ) + self.assertListing2Dump(ascii_listing,program_dump, + # debug=True + ) + self.assertDump2Listing(ascii_listing,program_dump) + class RenumTests(BaseDragon32ApiTestCase): def test_renum01(self): @@ -478,7 +529,7 @@ def test_get_destinations_1(self): if __name__ == '__main__': from dragonlib.utils.logging_utils import setup_logging - setup_logging(log, + setup_logging( # level=1 # hardcore debug ;) # level=10 # DEBUG # level=20 # INFO @@ -492,6 +543,7 @@ def test_get_destinations_1(self): argv=( sys.argv[0], # "Dragon32BASIC_HighLevel_ApiTest.test_listing2program_strings_dont_in_comment", +# "Dragon32BASIC_HighLevel_ApiTest.test_two_byte_line_numbers", ), # verbosity=1, verbosity=2, diff --git a/dragonlib/tests/test_basic_parser.py b/dragonlib/tests/test_basic_parser.py index 485f19be..b21cd9f0 100644 --- a/dragonlib/tests/test_basic_parser.py +++ b/dragonlib/tests/test_basic_parser.py @@ -286,10 +286,11 @@ def test_spaces_after_line_no(self): # print_parsed_lines=True ) + if __name__ == "__main__": from dragonlib.utils.logging_utils import setup_logging - setup_logging(log, + setup_logging( # level=1 # hardcore debug ;) # level=10 # DEBUG # level=20 # INFO From 5d9a223f9700a5c1ce79f87e0bd9709af35f4670 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Tue, 30 Sep 2014 10:59:13 +0200 Subject: [PATCH 135/151] More enhanced "display tokens" in editor --- basic_editor/editor.py | 124 +------------------------------- basic_editor/status_bar.py | 46 ++++++++++++ basic_editor/token_window.py | 134 +++++++++++++++++++++++++++++++++++ 3 files changed, 182 insertions(+), 122 deletions(-) create mode 100644 basic_editor/status_bar.py create mode 100644 basic_editor/token_window.py diff --git a/basic_editor/editor.py b/basic_editor/editor.py index 908bcfb3..451d14b1 100644 --- a/basic_editor/editor.py +++ b/basic_editor/editor.py @@ -20,11 +20,12 @@ import sys from basic_editor.scrolled_text import ScrolledText +from basic_editor.status_bar import MultiStatusBar +from basic_editor.token_window import TokenWindow import dragonlib from basic_editor.editor_base import BaseExtension from basic_editor.highlighting import TkTextHighlighting from dragonlib.utils.auto_shift import invert_shift -from dragonlib.utils.logging_utils import pformat_program_dump log = logging.getLogger(__name__) @@ -41,24 +42,6 @@ import tkMessageBox as messagebox -class MultiStatusBar(tkinter.Frame): - """ - code from idlelib.MultiStatusBar.MultiStatusBar - """ - - def __init__(self, master, **kw): - tkinter.Frame.__init__(self, master, **kw) - self.labels = {} - - def set_label(self, name, text='', side=tkinter.LEFT): - if name not in self.labels: - label = tkinter.Label(self, bd=1, relief=tkinter.SUNKEN, anchor=tkinter.W) - label.pack(side=side) - self.labels[name] = label - else: - label = self.labels[name] - label.config(text=text) - class TkTextHighlightCurrentLine(BaseExtension): after_id = None @@ -93,109 +76,6 @@ def __update_interval(self): self.after_id = self.text.after(10, self.__update_interval) -class TokenWindow(object): - def __init__(self, cfg, master): - self.cfg = cfg - self.machine_api = self.cfg.machine_api - - self.root = tkinter.Toplevel(master) - self.root.geometry("+%d+%d" % ( - master.winfo_rootx() + master.winfo_width(), - master.winfo_y() # FIXME: Different on linux. - )) - self.root.columnconfigure(0, weight=1) - self.root.rowconfigure(0, weight=1) - self.base_title = "%s - Tokens" % self.cfg.MACHINE_NAME - self.root.title(self.base_title) - - self.text = ScrolledText( - master=self.root, height=30, width=80 - ) - self.text.config( - background="#ffffff", foreground="#000000", - highlightthickness=0, - font=('courier', 11), - ) - self.text.grid(row=0, column=0, sticky=tkinter.NSEW) - - self.set_status_bar() # Create widget, add bindings and after_idle() update - - self.text.after_idle(self.set_token_info) - - def display_listing(self, content): - program_dump = self.machine_api.ascii_listing2program_dump(content) - formated_dump = pformat_program_dump(program_dump) - - self.text.insert(tkinter.END, formated_dump) - - self.text.bind("", self.on_mouse_move) - - def on_mouse_move(self, event): - index = self.text.index("@%s,%s" % (event.x, event.y)) - - try: - word = self.text.get("%s wordstart" % index, "%s wordend" % index) - except tkinter.TclError as err: - log.critical("TclError: %s", err) - return - - try: - token_value = int(word, 16) - except ValueError: - return - - log.critical("$%x", token_value) - basic_word = self.machine_api.token_util.token2ascii(token_value) - - info = "%s $%02x == %r" % (index, token_value, basic_word) - - try: - selection_index="%s-%s" % (self.text.index("sel.first"), self.text.index("sel.last")) - selection=self.text.selection_get() - except tkinter.TclError: - # no selection - pass - else: - log.critical(" selection: %s: %r", selection_index, selection) - - selection = selection.replace("$", "") - token_values = [int(part, 16) for part in selection.split() if part.strip()] - log.critical("values: %r", token_values) - basic_selection = self.machine_api.token_util.tokens2ascii(token_values) - - info += " - selection: %r" % basic_selection - - self.status_bar.set_label("cursor_info", info) - - ########################################################################### - # Status bar - - def set_status_bar(self): - self.status_bar = MultiStatusBar(self.root) - if sys.platform == "darwin": - # Insert some padding to avoid obscuring some of the statusbar - # by the resize widget. - self.status_bar.set_label('_padding1', ' ', side=tkinter.RIGHT) - self.status_bar.grid(row=1, column=0) - - self.text.bind("<>", self.set_line_and_column) - self.text.event_add("<>", - "", "") - self.text.after_idle(self.set_line_and_column) - - def set_line_and_column(self, event=None): - line, column = self.text.index(tkinter.INSERT).split('.') - self.status_bar.set_label('column', 'Column: %s' % column) - self.status_bar.set_label('line', 'Line: %s' % line) - - ########################################################################### - - def set_token_info(self, event=None): - line, column = self.text.index(tkinter.INSERT).split('.') - - - - class EditorWindow(object): FILETYPES = [# For filedialog ("BASIC Listings", "*.bas", "TEXT"), diff --git a/basic_editor/status_bar.py b/basic_editor/status_bar.py new file mode 100644 index 00000000..b2c18399 --- /dev/null +++ b/basic_editor/status_bar.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python +# encoding:utf8 + +""" + DragonPy - Dragon 32 emulator in Python + ======================================= + + Some code borrowed from Python IDLE + + :created: 2014 by Jens Diemer - www.jensdiemer.de + :copyleft: 2014 by the DragonPy team, see AUTHORS for more details. + :license: GNU GPL v3 or above, see LICENSE for more details. +""" + +from __future__ import absolute_import, division, print_function + +import logging + + +log = logging.getLogger(__name__) + +try: + # Python 3 + import tkinter +except ImportError: + # Python 2 + import Tkinter as tkinter + + +class MultiStatusBar(tkinter.Frame): + """ + code from idlelib.MultiStatusBar.MultiStatusBar + """ + + def __init__(self, master, **kw): + tkinter.Frame.__init__(self, master, **kw) + self.labels = {} + + def set_label(self, name, text='', side=tkinter.LEFT): + if name not in self.labels: + label = tkinter.Label(self, bd=1, relief=tkinter.SUNKEN, anchor=tkinter.W) + label.pack(side=side) + self.labels[name] = label + else: + label = self.labels[name] + label.config(text=text) \ No newline at end of file diff --git a/basic_editor/token_window.py b/basic_editor/token_window.py new file mode 100644 index 00000000..41713b99 --- /dev/null +++ b/basic_editor/token_window.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python +# encoding:utf8 + +""" + DragonPy - Dragon 32 emulator in Python + ======================================= + + Some code borrowed from Python IDLE + + :created: 2014 by Jens Diemer - www.jensdiemer.de + :copyleft: 2014 by the DragonPy team, see AUTHORS for more details. + :license: GNU GPL v3 or above, see LICENSE for more details. +""" + +from __future__ import absolute_import, division, print_function + +import logging +import sys + +from basic_editor.scrolled_text import ScrolledText +from basic_editor.status_bar import MultiStatusBar +from dragonlib.utils.logging_utils import pformat_program_dump + + +log = logging.getLogger(__name__) + +try: + # Python 3 + import tkinter +except ImportError: + # Python 2 + import Tkinter as tkinter + + +class TokenWindow(object): + def __init__(self, cfg, master): + self.cfg = cfg + self.machine_api = self.cfg.machine_api + + self.root = tkinter.Toplevel(master) + self.root.geometry("+%d+%d" % ( + master.winfo_rootx() + master.winfo_width(), + master.winfo_y() # FIXME: Different on linux. + )) + self.root.columnconfigure(0, weight=1) + self.root.rowconfigure(0, weight=1) + self.base_title = "%s - Tokens" % self.cfg.MACHINE_NAME + self.root.title(self.base_title) + + self.text = ScrolledText( + master=self.root, height=30, width=80 + ) + self.text.config( + background="#ffffff", foreground="#000000", + highlightthickness=0, + font=('courier', 11), + ) + self.text.grid(row=0, column=0, sticky=tkinter.NSEW) + + self.set_status_bar() # Create widget, add bindings and after_idle() update + + self.text.after_idle(self.set_token_info) + + def display_listing(self, content): + program_dump = self.machine_api.ascii_listing2program_dump(content) + formated_dump = pformat_program_dump(program_dump) + + self.text.insert(tkinter.END, formated_dump) + + self.text.bind("", self.on_mouse_move) + + def on_mouse_move(self, event): + index = self.text.index("@%s,%s" % (event.x, event.y)) + + try: + word = self.text.get("%s wordstart" % index, "%s wordend" % index) + except tkinter.TclError as err: + log.critical("TclError: %s", err) + return + + try: + token_value = int(word, 16) + except ValueError: + return + + log.critical("$%x", token_value) + basic_word = self.machine_api.token_util.token2ascii(token_value) + + info = "%s $%02x == %r" % (index, token_value, basic_word) + + try: + selection_index = "%s-%s" % (self.text.index("sel.first"), self.text.index("sel.last")) + selection = self.text.selection_get() + except tkinter.TclError: + # no selection + pass + else: + log.critical(" selection: %s: %r", selection_index, selection) + + selection = selection.replace("$", "") + token_values = [int(part, 16) for part in selection.split() if part.strip()] + log.critical("values: %r", token_values) + basic_selection = self.machine_api.token_util.tokens2ascii(token_values) + + info += " - selection: %r" % basic_selection + + self.status_bar.set_label("cursor_info", info) + + # ########################################################################## + # Status bar + + def set_status_bar(self): + self.status_bar = MultiStatusBar(self.root) + if sys.platform == "darwin": + # Insert some padding to avoid obscuring some of the statusbar + # by the resize widget. + self.status_bar.set_label('_padding1', ' ', side=tkinter.RIGHT) + self.status_bar.grid(row=1, column=0) + + self.text.bind("<>", self.set_line_and_column) + self.text.event_add("<>", + "", "") + self.text.after_idle(self.set_line_and_column) + + def set_line_and_column(self, event=None): + line, column = self.text.index(tkinter.INSERT).split('.') + self.status_bar.set_label('column', 'Column: %s' % column) + self.status_bar.set_label('line', 'Line: %s' % line) + + ########################################################################### + + def set_token_info(self, event=None): + line, column = self.text.index(tkinter.INSERT).split('.') + From 7d4c457b868d6f9537957e1a4d00eafb0ac20b60 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Tue, 30 Sep 2014 11:12:20 +0200 Subject: [PATCH 136/151] activate undo in BASIC editor --- basic_editor/scrolled_text.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/basic_editor/scrolled_text.py b/basic_editor/scrolled_text.py index cd86251a..7c93ea0f 100644 --- a/basic_editor/scrolled_text.py +++ b/basic_editor/scrolled_text.py @@ -36,7 +36,7 @@ def __init__(self, master=None, **kw): xscrollbar.grid(row=1, column=0, sticky=tkinter.EW) yscrollbar.grid(row=0, column=1, sticky=tkinter.NS) - _defaults_options={'wrap': tkinter.NONE} + _defaults_options={"wrap": tkinter.NONE, "undo": tkinter.YES} options = _defaults_options.copy() options.update(kw) options.update({'yscrollcommand': yscrollbar.set}) From ad84dadb428d6b6c08045dbf97205f466c92ce08 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Tue, 30 Sep 2014 11:59:43 +0200 Subject: [PATCH 137/151] PY3 bugfix --- dragonpy/components/memory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dragonpy/components/memory.py b/dragonpy/components/memory.py index 566ca40c..41c6b9e9 100644 --- a/dragonpy/components/memory.py +++ b/dragonpy/components/memory.py @@ -175,7 +175,7 @@ def load(self, address, data): for ea, datum in enumerate(data, address): try: self._mem[ea] = datum - except OverflowError, err: + except OverflowError as err: msg="%s - datum=$%x ea=$%04x (load address was: $%04x - data length: %iBytes)" % ( err, datum, ea, address, len(data) ) From e9a8e66c9477161b88a601b9cef29c9c9d9ef20e Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Tue, 30 Sep 2014 13:29:47 +0200 Subject: [PATCH 138/151] Bugfix renum tool + renum INVADER.bas --- BASIC games/INVADER.bas | 186 ++++++++++++++++++------------------ dragonlib/api.py | 4 +- dragonlib/core/basic.py | 33 ++----- dragonlib/tests/test_api.py | 33 +++++-- 4 files changed, 129 insertions(+), 127 deletions(-) diff --git a/BASIC games/INVADER.bas b/BASIC games/INVADER.bas index 13abc96a..d9c1f9f7 100755 --- a/BASIC games/INVADER.bas +++ b/BASIC games/INVADER.bas @@ -1,93 +1,93 @@ -0 GOSUB10005:GOSUB10000:CLS:CLEAR5000:DIML(511),Y(542),T(542),K(255),L$,B$,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,AK,BC,N1,LV,SC:GOTO900 -1 RETURN -2 FORU=I TO B:RESET(Y,K):K=K-I:ONPOINT(Y,K)GOTO5,5,5,3,3,3,3,27:SET(Y,K,I):NEXT:RESET(Y,K):RETURN -3 H=INT(K/W)*G+INT(Y/W):PRINT@H-I,CHR$(N2)CHR$(N2+RND(N7))CHR$(N2+RND(N7));:AK=AK+I:IFAK>N1 THENU=B:NEXT:L=E:RETURN -4 VP=VARPTR(L$):FORZ=-I TOI:POKE(PEEK(VP+W)*V)+PEEK(VP+R)+(H-L)+Z,N2:NEXT:SET(Y,K,I):U=B:K=O:NEXT:RETURN -5 RESET(Y,K):U=B:K=O:Y=O:NEXT:RESET(Y,K):RETURN -6 RESET(C,M):M=M+W:ONPOINT(C,M)GOTO24,14,14:SET(C,M,I):RETURN -7 T=R:NEXT:OND+I GOTO8,8:S=L:L=E:NEXT:D=I:F=O:E=N9:X=S1:GOTO9 -8 S=L:L=E:NEXT:D=-I:F=N1:E=N1:X=R:MS=MS+W:PRINT@MS,M$;:IFMS>N8 THENPRINT@MS,LEFT$(B$,5);:MS=O -9 BC=BC+I:IFBC>(G-AK)/W AND SB)+I GOTO1:P=P-W:Q=Q-I:RETURN -16 ON(PO AND AK=32 THEN160 -100 PRINT@MS,LEFT$(B$,6);:IFS<448 THEN FORL=S TO 479 STEP32:PRINT@L-N1,LEFT$(B$,L(L-N1));:PRINT@L,LEFT$(L$,L(L));:SOUND1,2:NEXT -105 PRINT@0, "YOU ARE DEAD. ";:SC=SC+(AK*LV):PRINT"SCORE=";SC:IFSC>HS THEN HS=SC -110 PRINT@32,"PLAY AGAIN (Y/N)? ";:PRINT"HIGH=";HS -115 IFAK<32 THEN PRINT@196,"THE ALIENS HAVE LANDED!";:SOUND100,15 -120 I$=INKEY$:IFI$=""THEN120 -130 IFI$="Y" THEN 1570 -140 IFI$="N"THENGOSUB10010:GOSUB10003:END -150 GOTO120 -160 PRINT@0,"ANOTHER WAVE IS COMING!" -170 SOUND100,3:SOUND50,3:SOUND100,3:SOUND50,5:SOUND50,5:SOUND100,3:SOUND50,3 -180 SC=SC+(32*LV):PRINT@266,"SCORE=";SC;:FORT=1TO2000:NEXT -185 IFNP=SS THEN PRINT@198,"+100 SURVIVAL BONUS";:FORT=1 TO 2:FORZ=1 TO 24:READAA,BB:SOUNDAA,BB:NEXT:RESTORE:NEXT:SC=SC+100 -190 XM=XM+I:IFXM=2 THEN GOSUB200 -195 GOSUB1600:GOTO10 -200 XM=0:IFNP=3 THEN 320 -300 PRINT@198,"*SHIELDS REPAIRED!*"; -310 NP=NP+1 -315 FORT=1 TO 10:POKE1535,48+NP+64:PRINT@Q,Q$;:FORZ=1 TO 200:NEXT:POKE1535,48+NP:PRINT@Q,P$;:FORZ=1 TO 200:NEXT:NEXT -320 RETURN -900 PRINT@231,"* SPACE INVADERS *"; -910 PRINT@291,"FOR THE MC-10 BY JIM GERRIE"; -920 PRINT@485,"PRESS ANY KEY TO BEGIN"; -930 P$=INKEY$:R=RND(1000):IFP$="" THEN 930 -999 CLS:PRINT"PLEASE WAIT..." -1000 FORT=1TO32:B$=B$+CHR$(128):NEXT -1010 O=0:I=1:W=2:R=3:B=4:G=32:N=29:V=256:N1=31 -1020 K(65)=1:K(83)=2:K(32)=3:K(8)=1:K(9)=2:A=17023 -1030 DIMN2,N3,N4,N5,N6,N7,N8,N9,MS,NS,XM,NP,SS,Q$,P$,I$,M$,V(3),VP,S1 -1040 P$=CHR$(128)+CHR$(135+32)+CHR$(139+32)+CHR$(128):Q$=CHR$(128)+CHR$(135+16)+CHR$(139+16)+CHR$(128) -1050 N2=128:N3=27:N4=58:N5=28:N6=480:N7=14:N8=24:N9=479:S1=61:SH$=CHR$(135+32)+CHR$(139+32) -1500 FORT=0TO256:L(T)=224:NEXT:FORT=257TO479:L(T)=L(T-1)-1:NEXT -1510 FORT=0TO286:T(T)=3:Y(T)=INT((T/16)+.1):NEXT -1520 FORT=287TO350:T(T)=2:Y(T)=INT((T/16)+.1):NEXT -1530 FORT=351TO414:T(T)=1:Y(T)=INT((T/16)+.1):NEXT -1540 FORT=415TO478:T(T)=0:Y(T)=INT((T/16)+.1):NEXT -1550 FORT=479TO542:T(T)=0:Y(T)=28:NEXT -1560 FORT=0TO3:V(T)=B*T:NEXT:M$=CHR$(128)+CHR$(128)+CHR$(241)+CHR$(255)+CHR$(251) -1570 SC=0:LV=0:NS=0:NP=3:MS=0:XM=0:GOSUB1600 -1580 GOTO10 -1600 CLS0:D=1:E=479:X=61:V(0)=0:NS=NS+1:LV=LV+1:IFLV>4 THEN LV=4 -1605 PRINT@480,NS;" ";:FORT=1504TO1508:POKET,PEEK(T)-64:NEXT:FORT=1509TO1535:POKET,G:NEXT:POKE1535,48+NP -1610 FORT=1TO3:PRINT@348+(9*T),CHR$(135);CHR$(143);CHR$(139);:PRINT@348+(9*T)+32,CHR$(143);CHR$(143);CHR$(143);:NEXT -1630 P=31:Q=448+14:M=1:L=LV*32:S=L:AK=0:BC=0:F=0:H=0:K=0:U=0:Z=0 -1700 L$="":C=32:FORY=1TO4:C=C+16:FORT=1TO8:L$=L$+CHR$(128+C)+CHR$(133+C)+CHR$(141+C):NEXT:IFY=4 THEN L$=L$+LEFT$(B$,W):GOTO1715 -1712 L$=L$+B$+LEFT$(B$,8) -1715 NEXT:C=0:SS=NP:RETURN -2100 DATA 89,1,125,1,147,1,176,1,147,1,125,1 -2110 DATA 89,1,125,1,147,1,89,1,147,1,125,1 -2120 DATA 89,1,125,1,147,1,176,1,147,1,125,1 -2130 DATA 89,1,125,1,147,1,89,1,147,1,125,1 -10000 IF PEEK(65535)=27 THEN POKE65497,0:GOTO10002 :ELSE CLS:INPUT"CAN YOUR COMPUTER HANDLE DOUBLE SPEED (Y/N)";A$ -10001 IF A$="Y" THEN POKE65495,0 :ELSE IF A$<>"N" THEN10000 -10002 CLS:RETURN -10003 IF PEEK(65535)=27 THEN POKE65496,0 :ELSE POKE65494,0 -10004 RETURN -10005 :' ENABLE DRAGON SPEEDKEY -10006 IF PEEK(65535)<>180 THEN 10008 -10007 IF PEEK(269)+PEEK(270)<>1 THEN POKE65283,52:POKE256,116:POKE257,1:POKE258,81:POKE259,126:POKE260,PEEK(269):POKE 261,PEEK(270):POKE269,1:POKE270,0:POKE65283,53 -10008 RETURN -10009 :' DISABLE DRAGON SPEEDKEY -10010 IF PEEK(65535)<>180 THEN 10012 -10011 IF PEEK(269)+PEEK(270)=1 THEN POKE65283,52:POKE269,PEEK(260):POKE270,PEEK(261):POKE65283,53 -10012 RETURN +10 GOSUB860:GOSUB810:CLS:CLEAR5000:DIML(511),Y(542),T(542),K(255),L$,B$,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,AK,BC,N1,LV,SC:GOTO500 +20 RETURN +30 FORU=I TO B:RESET(Y,K):K=K-I:ONPOINT(Y,K)GOTO60,60,60,40,40,40,40,270:SET(Y,K,I):NEXT:RESET(Y,K):RETURN +40 H=INT(K/W)*G+INT(Y/W):PRINT@H-I,CHR$(N2)CHR$(N2+RND(N7))CHR$(N2+RND(N7));:AK=AK+I:IFAK>N1 THENU=B:NEXT:L=E:RETURN +50 VP=VARPTR(L$):FORZ=-I TOI:POKE(PEEK(VP+W)*V)+PEEK(VP+R)+(H-L)+Z,N2:NEXT:SET(Y,K,I):U=B:K=O:NEXT:RETURN +60 RESET(Y,K):U=B:K=O:Y=O:NEXT:RESET(Y,K):RETURN +70 RESET(C,M):M=M+W:ONPOINT(C,M)GOTO240,150,150:SET(C,M,I):RETURN +80 T=R:NEXT:OND+I GOTO90,90:S=L:L=E:NEXT:D=I:F=O:E=N9:X=S1:GOTO100 +90 S=L:L=E:NEXT:D=-I:F=N1:E=N1:X=R:MS=MS+W:PRINT@MS,M$;:IFMS>N8 THENPRINT@MS,LEFT$(B$,5);:MS=O +100 BC=BC+I:IFBC>(G-AK)/W AND SB)+I GOTO20:P=P-W:Q=Q-I:RETURN +170 ON(PO AND AK=32 THEN390 +310 PRINT@MS,LEFT$(B$,6);:IFS<448 THEN FORL=S TO 479 STEP32:PRINT@L-N1,LEFT$(B$,L(L-N1));:PRINT@L,LEFT$(L$,L(L));:SOUND1,2:NEXT +320 PRINT@0, "YOU ARE DEAD. ";:SC=SC+(AK*LV):PRINT"SCORE=";SC:IFSC>HS THEN HS=SC +330 PRINT@32,"PLAY AGAIN (Y/N)? ";:PRINT"HIGH=";HS +340 IFAK<32 THEN PRINT@196,"THE ALIENS HAVE LANDED!";:SOUND100,15 +350 I$=INKEY$:IFI$=""THEN350 +360 IFI$="Y" THEN 680 +370 IFI$="N"THENGOSUB910:GOSUB840:END +380 GOTO350 +390 PRINT@0,"ANOTHER WAVE IS COMING!" +400 SOUND100,3:SOUND50,3:SOUND100,3:SOUND50,5:SOUND50,5:SOUND100,3:SOUND50,3 +410 SC=SC+(32*LV):PRINT@266,"SCORE=";SC;:FORT=1TO2000:NEXT +420 IFNP=SS THEN PRINT@198,"+100 SURVIVAL BONUS";:FORT=1 TO 2:FORZ=1 TO 24:READAA,BB:SOUNDAA,BB:NEXT:RESTORE:NEXT:SC=SC+100 +430 XM=XM+I:IFXM=2 THEN GOSUB450 +440 GOSUB700:GOTO110 +450 XM=0:IFNP=3 THEN 490 +460 PRINT@198,"*SHIELDS REPAIRED!*"; +470 NP=NP+1 +480 FORT=1 TO 10:POKE1535,48+NP+64:PRINT@Q,Q$;:FORZ=1 TO 200:NEXT:POKE1535,48+NP:PRINT@Q,P$;:FORZ=1 TO 200:NEXT:NEXT +490 RETURN +500 PRINT@231,"* SPACE INVADERS *"; +510 PRINT@291,"FOR THE MC-10 BY JIM GERRIE"; +520 PRINT@485,"PRESS ANY KEY TO BEGIN"; +530 P$=INKEY$:R=RND(1000):IFP$="" THEN 530 +540 CLS:PRINT"PLEASE WAIT..." +550 FORT=1TO32:B$=B$+CHR$(128):NEXT +560 O=0:I=1:W=2:R=3:B=4:G=32:N=29:V=256:N1=31 +570 K(65)=1:K(83)=2:K(32)=3:K(8)=1:K(9)=2:A=17023 +580 DIMN2,N3,N4,N5,N6,N7,N8,N9,MS,NS,XM,NP,SS,Q$,P$,I$,M$,V(3),VP,S1 +590 P$=CHR$(128)+CHR$(135+32)+CHR$(139+32)+CHR$(128):Q$=CHR$(128)+CHR$(135+16)+CHR$(139+16)+CHR$(128) +600 N2=128:N3=27:N4=58:N5=28:N6=480:N7=14:N8=24:N9=479:S1=61:SH$=CHR$(135+32)+CHR$(139+32) +610 FORT=0TO256:L(T)=224:NEXT:FORT=257TO479:L(T)=L(T-1)-1:NEXT +620 FORT=0TO286:T(T)=3:Y(T)=INT((T/16)+.1):NEXT +630 FORT=287TO350:T(T)=2:Y(T)=INT((T/16)+.1):NEXT +640 FORT=351TO414:T(T)=1:Y(T)=INT((T/16)+.1):NEXT +650 FORT=415TO478:T(T)=0:Y(T)=INT((T/16)+.1):NEXT +660 FORT=479TO542:T(T)=0:Y(T)=28:NEXT +670 FORT=0TO3:V(T)=B*T:NEXT:M$=CHR$(128)+CHR$(128)+CHR$(241)+CHR$(255)+CHR$(251) +680 SC=0:LV=0:NS=0:NP=3:MS=0:XM=0:GOSUB700 +690 GOTO110 +700 CLS0:D=1:E=479:X=61:V(0)=0:NS=NS+1:LV=LV+1:IFLV>4 THEN LV=4 +710 PRINT@480,NS;" ";:FORT=1504TO1508:POKET,PEEK(T)-64:NEXT:FORT=1509TO1535:POKET,G:NEXT:POKE1535,48+NP +720 FORT=1TO3:PRINT@348+(9*T),CHR$(135);CHR$(143);CHR$(139);:PRINT@348+(9*T)+32,CHR$(143);CHR$(143);CHR$(143);:NEXT +730 P=31:Q=448+14:M=1:L=LV*32:S=L:AK=0:BC=0:F=0:H=0:K=0:U=0:Z=0 +740 L$="":C=32:FORY=1TO4:C=C+16:FORT=1TO8:L$=L$+CHR$(128+C)+CHR$(133+C)+CHR$(141+C):NEXT:IFY=4 THEN L$=L$+LEFT$(B$,W):GOTO760 +750 L$=L$+B$+LEFT$(B$,8) +760 NEXT:C=0:SS=NP:RETURN +770 DATA 89,1,125,1,147,1,176,1,147,1,125,1 +780 DATA 89,1,125,1,147,1,89,1,147,1,125,1 +790 DATA 89,1,125,1,147,1,176,1,147,1,125,1 +800 DATA 89,1,125,1,147,1,89,1,147,1,125,1 +810 IF PEEK(65535)=27 THEN POKE65497,0:GOTO830 :ELSE CLS:INPUT"CAN YOUR COMPUTER HANDLE DOUBLE SPEED (Y/N)";A$ +820 IF A$="Y" THEN POKE65495,0 :ELSE IF A$<>"N" THEN810 +830 CLS:RETURN +840 IF PEEK(65535)=27 THEN POKE65496,0 :ELSE POKE65494,0 +850 RETURN +860 :' ENABLE DRAGON SPEEDKEY +870 IF PEEK(65535)<>180 THEN 890 +880 IF PEEK(269)+PEEK(270)<>1 THEN POKE65283,52:POKE256,116:POKE257,1:POKE258,81:POKE259,126:POKE260,PEEK(269):POKE 261,PEEK(270):POKE269,1:POKE270,0:POKE65283,53 +890 RETURN +900 :' DISABLE DRAGON SPEEDKEY +910 IF PEEK(65535)<>180 THEN 930 +920 IF PEEK(269)+PEEK(270)=1 THEN POKE65283,52:POKE269,PEEK(260):POKE270,PEEK(261):POKE65283,53 +930 RETURN \ No newline at end of file diff --git a/dragonlib/api.py b/dragonlib/api.py index 41c667de..9413166e 100644 --- a/dragonlib/api.py +++ b/dragonlib/api.py @@ -29,9 +29,7 @@ class BaseAPI(object): RENUM_REGEX = r""" - (?P GOTO|GOSUB|THEN|ELSE ) (?P\s*) (?P\d+) - | - (?P ON.+?GOTO|ON.+?GOSUB ) (?P\s*) (?P[\d*,\s*]+) + (?P GOTO|GOSUB|THEN|ELSE ) (?P\s*) (?P[\d*,\s*]+) """ def __init__(self): diff --git a/dragonlib/core/basic.py b/dragonlib/core/basic.py index 84ff3f8d..30271f85 100644 --- a/dragonlib/core/basic.py +++ b/dragonlib/core/basic.py @@ -378,6 +378,7 @@ def renum(self, ascii_listing): new_number *= 10 line = self.line_no_regex.sub("%s\g" % new_number, line) new_line = self.renum_regex.sub(self.renum_inline, line) + log.debug("%r -> %r", line, new_line) new_listing.append(new_line) return "\n".join(new_listing) @@ -387,19 +388,16 @@ def get_destinations(self, ascii_listing): """ self.destinations = set() def collect_destinations(matchobj): - numbers = matchobj.group("on_goto_no") + numbers = matchobj.group("no") if numbers: self.destinations.update(set( [n.strip() for n in numbers.split(",")] )) - number = matchobj.group("no") - if number: - self.destinations.add(number) for line in self._iter_lines(ascii_listing): self.renum_regex.sub(collect_destinations, line) - return sorted([int(no) for no in self.destinations]) + return sorted([int(no) for no in self.destinations if no]) def _iter_lines(self, ascii_listing): lines = ascii_listing.splitlines() @@ -418,17 +416,9 @@ def _get_new_line_number(self, line, old_number): new_number = old_number return new_number - def _replace_statement(self, matchobj): - old_number = matchobj.group("no") - new_number = self._get_new_line_number(matchobj.group(0), old_number) - return "".join([ - matchobj.group("statement"), - matchobj.group("space"), - new_number - ]) - - def _replace_on_goto(self, matchobj): - old_numbers = matchobj.group("on_goto_no") + def renum_inline(self, matchobj): +# log.critical(matchobj.groups()) + old_numbers = matchobj.group("no") if old_numbers[-1] == " ": # e.g.: space before comment: ON X GOTO 1,2 ' Comment space_after = " " @@ -440,18 +430,11 @@ def _replace_on_goto(self, matchobj): for old_number in old_numbers ] return "".join([ - matchobj.group("on_goto_statement"), - matchobj.group("on_goto_space"), + matchobj.group("statement"), + matchobj.group("space"), ",".join(new_numbers), space_after ]) - def renum_inline(self, matchobj): -# log.critical(matchobj.groups()) - if matchobj.group("on_goto_statement"): - return self._replace_on_goto(matchobj) - else: - return self._replace_statement(matchobj) - def create_renum_dict(self, ascii_listing): old_numbers = [match[0] for match in self.line_no_regex.findall(ascii_listing)] renum_dict = {} diff --git a/dragonlib/tests/test_api.py b/dragonlib/tests/test_api.py index a3455093..485c26e2 100644 --- a/dragonlib/tests/test_api.py +++ b/dragonlib/tests/test_api.py @@ -526,16 +526,36 @@ def test_get_destinations_1(self): [10, 20, 30, 40, 50, 70, 999] ) + def test_on_gosub_and_goto(self): + old_listing = self._prepare_text(""" + 2 PRINT "2" + 13 ON X GOSUB 18 :ONY GOSUB 2,2:NEXT:GOTO99 + 18 PRINT "18" + 99 PRINT "99" + """) + # print(old_listing) + # print("-"*79) + new_listing = self.dragon32api.renum_ascii_listing(old_listing) + # print(new_listing) + self.assertEqual(new_listing, self._prepare_text(""" + 10 PRINT "2" + 20 ON X GOSUB 30 :ONY GOSUB 10,10:NEXT:GOTO40 + 30 PRINT "18" + 40 PRINT "99" + """)) + + + if __name__ == '__main__': from dragonlib.utils.logging_utils import setup_logging setup_logging( -# level=1 # hardcore debug ;) -# level=10 # DEBUG -# level=20 # INFO -# level=30 # WARNING -# level=40 # ERROR - level=50 # CRITICAL/FATAL + level=1 # hardcore debug ;) + # level=10 # DEBUG + # level=20 # INFO + # level=30 # WARNING + # level=40 # ERROR + # level=50 # CRITICAL/FATAL ) unittest.main( @@ -544,6 +564,7 @@ def test_get_destinations_1(self): sys.argv[0], # "Dragon32BASIC_HighLevel_ApiTest.test_listing2program_strings_dont_in_comment", # "Dragon32BASIC_HighLevel_ApiTest.test_two_byte_line_numbers", +# "RenumTests.test_on_gosub_and_goto" ), # verbosity=1, verbosity=2, From c5d5a353a18eca82ca9a6e4ff137525d133fd986 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Tue, 30 Sep 2014 13:30:30 +0200 Subject: [PATCH 139/151] add copy&paste in BASIC editor --- basic_editor/scrolled_text.py | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/basic_editor/scrolled_text.py b/basic_editor/scrolled_text.py index 7c93ea0f..fb5f72ba 100644 --- a/basic_editor/scrolled_text.py +++ b/basic_editor/scrolled_text.py @@ -8,6 +8,10 @@ :copyleft: 2014 by the DragonPy team, see AUTHORS for more details. :license: GNU GPL v3 or above, see LICENSE for more details. """ +from __future__ import absolute_import, division, print_function + +import logging +log = logging.getLogger(__name__) try: # Python 3 @@ -49,7 +53,36 @@ def __init__(self, master=None, **kw): xscrollbar.config(command=self.xview) yscrollbar.config(command=self.yview) - + self.bind('', self.event_select_all) + self.bind('', self.event_cut) + self.bind('', self.event_copy) + self.bind('', self.event_paste) + + def event_select_all(self, event=None): + log.critical("Select all.") + self.tag_add(tkinter.SEL, "1.0", tkinter.END) + self.mark_set(tkinter.INSERT, "1.0") + self.see(tkinter.INSERT) + return "break" + + def event_cut(self, event=None): + if self.tag_ranges(tkinter.SEL): + self.event_copy() + self.delete(tkinter.SEL_FIRST, tkinter.SEL_LAST) + + def event_copy(self, event=None): + if self.tag_ranges(tkinter.SEL): + text = self.get(tkinter.SEL_FIRST, tkinter.SEL_LAST) + self.clipboard_clear() + self.clipboard_append(text) + + def event_paste(self, event=None): + text = self.selection_get(selection='CLIPBOARD') + if text: + self.insert(tkinter.INSERT, text) + self.tag_remove(tkinter.SEL, '1.0', tkinter.END) + self.see(tkinter.INSERT) + def __str__(self): return str(self.frame) From 26f7adfee0e233c4b722f7b50dfd3c05ee56039c Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Tue, 30 Sep 2014 13:31:05 +0200 Subject: [PATCH 140/151] WIP: TextHighlight --- basic_editor/editor.py | 38 ++--------------------------------- basic_editor/editor_base.py | 33 +++++++++++++++++++++++++++++- basic_editor/highlighting.py | 38 +++++++++++++++++++++++++++++++++++ basic_editor/tkinter_utils.py | 35 ++++++++++++++++++++++++++++++++ 4 files changed, 107 insertions(+), 37 deletions(-) create mode 100644 basic_editor/tkinter_utils.py diff --git a/basic_editor/editor.py b/basic_editor/editor.py index 451d14b1..f196cddf 100644 --- a/basic_editor/editor.py +++ b/basic_editor/editor.py @@ -19,12 +19,11 @@ import string import sys +import dragonlib from basic_editor.scrolled_text import ScrolledText from basic_editor.status_bar import MultiStatusBar from basic_editor.token_window import TokenWindow -import dragonlib -from basic_editor.editor_base import BaseExtension -from basic_editor.highlighting import TkTextHighlighting +from basic_editor.highlighting import TkTextHighlighting, TkTextHighlightCurrentLine from dragonlib.utils.auto_shift import invert_shift @@ -43,39 +42,6 @@ -class TkTextHighlightCurrentLine(BaseExtension): - after_id = None - TAG_CURRENT_LINE = "current_line" - - def __init__(self, editor): - super(TkTextHighlightCurrentLine, self).__init__(editor) - - self.text.tag_config(self.TAG_CURRENT_LINE, background="#e8f2fe") - - self.current_line = None - self.__update_interval() - - def update(self, force=False): - """ highlight the current line """ - line_no = self.text.index(tkinter.INSERT).split('.')[0] - - if not force: - if line_no == self.current_line: -# log.critical("no highlight line needed.") - return - -# log.critical("highlight line: %s" % line_no) - self.current_line = line_no - - self.text.tag_remove(self.TAG_CURRENT_LINE, "1.0", "end") - self.text.tag_add(self.TAG_CURRENT_LINE, "%s.0" % line_no, "%s.0+1lines" % line_no) - - def __update_interval(self): - """ highlight the current line """ - self.update() - self.after_id = self.text.after(10, self.__update_interval) - - class EditorWindow(object): FILETYPES = [# For filedialog ("BASIC Listings", "*.bas", "TEXT"), diff --git a/basic_editor/editor_base.py b/basic_editor/editor_base.py index 6be53ac8..ad167680 100644 --- a/basic_editor/editor_base.py +++ b/basic_editor/editor_base.py @@ -1,7 +1,38 @@ +#!/usr/bin/env python +# encoding:utf8 + +""" + DragonPy - Dragon 32 emulator in Python + ======================================= + + Some code borrowed from Python IDLE + + :created: 2014 by Jens Diemer - www.jensdiemer.de + :copyleft: 2014 by the DragonPy team, see AUTHORS for more details. + :license: GNU GPL v3 or above, see LICENSE for more details. +""" + +from __future__ import absolute_import, division, print_function + +import logging + +log = logging.getLogger(__name__) + +try: + # Python 3 + import tkinter +except ImportError: + # Python 2 + import Tkinter as tkinter + +from basic_editor.tkinter_utils import TkTextTag + + class BaseExtension(object): def __init__(self, editor): self.editor = editor self.cfg=editor.cfg self.root = editor.root - self.text = editor.text # ScrolledText() instance \ No newline at end of file + self.text = editor.text # ScrolledText() instance + diff --git a/basic_editor/highlighting.py b/basic_editor/highlighting.py index 0c30e8bb..76943fa3 100644 --- a/basic_editor/highlighting.py +++ b/basic_editor/highlighting.py @@ -12,6 +12,7 @@ """ from __future__ import absolute_import, division, print_function +from basic_editor.tkinter_utils import TkTextTag from dragonlib.utils import six xrange = six.moves.xrange @@ -134,3 +135,40 @@ def recolorize(self): def removecolors(self): for tag in self.tagdefs: self.text.tag_remove(tag, "1.0", "end") + + + + +class TkTextHighlightCurrentLine(BaseExtension): + after_id = None + + def __init__(self, editor): + super(TkTextHighlightCurrentLine, self).__init__(editor) + + self.tag_current_line = TkTextTag(self.text, + background="#e8f2fe" + # relief='raised', borderwidth=1, + ) + + self.current_line = None + self.__update_interval() + + def update(self, force=False): + """ highlight the current line """ + line_no = self.text.index(tkinter.INSERT).split('.')[0] + + if not force: + if line_no == self.current_line: +# log.critical("no highlight line needed.") + return + +# log.critical("highlight line: %s" % line_no) + self.current_line = line_no + + self.text.tag_remove(self.tag_current_line.id, "1.0", "end") + self.text.tag_add(self.tag_current_line.id, "%s.0" % line_no, "%s.0+1lines" % line_no) + + def __update_interval(self): + """ highlight the current line """ + self.update() + self.after_id = self.text.after(10, self.__update_interval) \ No newline at end of file diff --git a/basic_editor/tkinter_utils.py b/basic_editor/tkinter_utils.py new file mode 100644 index 00000000..0e10310f --- /dev/null +++ b/basic_editor/tkinter_utils.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +# encoding:utf8 + +""" + DragonPy - Dragon 32 emulator in Python + ======================================= + + Some code borrowed from Python IDLE + + :created: 2014 by Jens Diemer - www.jensdiemer.de + :copyleft: 2014 by the DragonPy team, see AUTHORS for more details. + :license: GNU GPL v3 or above, see LICENSE for more details. +""" + +from __future__ import absolute_import, division, print_function + +import logging + + +log = logging.getLogger(__name__) + +try: + # Python 3 + import tkinter +except ImportError: + # Python 2 + import Tkinter as tkinter + + +class TkTextTag(object): + _id=0 + def __init__(self, text_widget, **config): + self.id = self._id + self._id+=1 + text_widget.tag_configure(self.id, config) \ No newline at end of file From 90c9345510ee9db1ec1fa7697c0293a45f31337e Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Tue, 30 Sep 2014 15:47:03 +0200 Subject: [PATCH 141/151] use tkinter event.keysym for special keys --- dragonpy/CoCo/config.py | 4 +- dragonpy/Dragon32/MC6821_PIA.py | 15 ----- dragonpy/Dragon32/config.py | 4 +- dragonpy/Dragon32/keyboard_map.py | 83 ++++++++++++++++----------- dragonpy/Dragon32/machine.py | 1 - dragonpy/Dragon32/periphery_dragon.py | 7 +-- dragonpy/core/gui.py | 26 ++++++--- dragonpy/tests/test_BASIC_Dragon32.py | 38 ++++++------ 8 files changed, 91 insertions(+), 87 deletions(-) mode change 100644 => 100755 dragonpy/core/gui.py diff --git a/dragonpy/CoCo/config.py b/dragonpy/CoCo/config.py index 6e098369..a48e1444 100644 --- a/dragonpy/CoCo/config.py +++ b/dragonpy/CoCo/config.py @@ -113,8 +113,8 @@ def basic_addresses_write(self, cycles, last_op_address, address, word): log.critical("%04x| write $%04x to $%04x", last_op_address, word, address) return word - def pia_keymatrix_result(self, char_or_code, pia0b): - return get_coco_keymatrix_pia_result(char_or_code, pia0b, auto_shift=True) + def pia_keymatrix_result(self, inkey, pia0b): + return get_coco_keymatrix_pia_result(inkey, pia0b) config = CoCo2bCfg diff --git a/dragonpy/Dragon32/MC6821_PIA.py b/dragonpy/Dragon32/MC6821_PIA.py index b810e7f6..f4cfa138 100644 --- a/dragonpy/Dragon32/MC6821_PIA.py +++ b/dragonpy/Dragon32/MC6821_PIA.py @@ -159,21 +159,6 @@ def internal_reset(self): self.current_input_char = None self.input_repead = 0 - #-------------------------------------------------------------------------- - - def key_down(self, char_or_code, block=False): - log.error( - "Add user key down %r to PIA input queue.", repr(char_or_code)) - self.user_input_queue.put(char_or_code, block=False) -# try: -# self.user_input_queue.put(char_or_code, block=block) -# except Queue.Full: -# log.log(level=99, -# msg="Ignore key press %s, because input queue is full!" % repr(char_or_code) -# ) - - #-------------------------------------------------------------------------- - def read_PIA1_A_data(self, cpu_cycles, op_address, address): """ read from 0xff20 -> PIA 1 A side Data reg. """ log.error("TODO: read from 0xff20 -> PIA 1 A side Data reg.") diff --git a/dragonpy/Dragon32/config.py b/dragonpy/Dragon32/config.py index 8f787466..5b8d7f92 100644 --- a/dragonpy/Dragon32/config.py +++ b/dragonpy/Dragon32/config.py @@ -153,8 +153,8 @@ def get_initial_RAM(self): return mem - def pia_keymatrix_result(self, char_or_code, pia0b): - return get_dragon_keymatrix_pia_result(char_or_code, pia0b, auto_shift=True) + def pia_keymatrix_result(self, inkey, pia0b): + return get_dragon_keymatrix_pia_result(inkey, pia0b) config = Dragon32Cfg diff --git a/dragonpy/Dragon32/keyboard_map.py b/dragonpy/Dragon32/keyboard_map.py index 02ac4d98..544edd92 100755 --- a/dragonpy/Dragon32/keyboard_map.py +++ b/dragonpy/Dragon32/keyboard_map.py @@ -104,22 +104,22 @@ "X": ((0, 5),), # X "Y": ((1, 5),), # Y "Z": ((2, 5),), # Z - 0x6f: ((3, 5),), # UP - 0x74: ((4, 5),), # DOWN - 0x71: ((5, 5),), # LEFT - 0x72: ((6, 5),), # RIGHT + 'Up': ((3, 5),), # UP + 'Down': ((4, 5),), # DOWN + 'Left': ((5, 5),), # LEFT + 'Right': ((6, 5),), # RIGHT " ": ((7, 5),), # " " (Space) - "\r": ((0, 6),), # ENTER - Char: '\r' - keycode: dez.: 36, hex: $24 - 0x6e: ((1, 6),), # CLEAR - $6e is "Home" / "Pos 1" button - "\x1b": ((2, 6),), # BREAK - $09 is "Escape" button + 'Return': ((0, 6),), # ENTER - Char: '\r' - keycode: dez.: 36, hex: $24 + 'Home': ((1, 6),), # CLEAR - $6e is "Home" / "Pos 1" button + 'Escape': ((2, 6),), # BREAK - $09 is "Escape" button - 0x32: ((7, 6),), # SHIFT (shift left) - 0x3e: ((7, 6),), # SHIFT (shift right) + 'Shift_L': ((7, 6),), # SHIFT (shift left) + 'Shift_R': ((7, 6),), # SHIFT (shift right) # Additional: - "\x08": ((5, 5),), # $08 is Backspace mapped to "LEFT" + 'BackSpace': ((5, 5),), # $08 is Backspace mapped to "LEFT" # Shifted keys: @@ -192,15 +192,12 @@ -def _get_col_row_values(char_or_code, keymap, auto_shift=True): - if auto_shift and isinstance(char_or_code, str): - char_or_code = invert_shift(char_or_code) - +def _get_col_row_values(inkey, keymap): try: - col_row_values = keymap[char_or_code] + col_row_values = keymap[inkey] except KeyError: col_row_values = () - log.critical("Key %s not supported or unknown.", repr(char_or_code)) + log.critical("Key %r not supported or unknown.", inkey) return col_row_values @@ -212,19 +209,36 @@ def _set_bits(pia0b, col_row_values): return result -def get_dragon_keymatrix_pia_result(char_or_code, pia0b, auto_shift=True): - col_row_values = _get_col_row_values(char_or_code, DRAGON_KEYMAP, auto_shift=auto_shift) +def get_dragon_keymatrix_pia_result(inkey, pia0b): + col_row_values = _get_col_row_values(inkey, DRAGON_KEYMAP) result = _set_bits(pia0b, col_row_values) return result -def get_coco_keymatrix_pia_result(char_or_code, pia0b, auto_shift=True): - col_row_values = _get_col_row_values(char_or_code, COCO_KEYMAP, auto_shift=auto_shift) +def get_coco_keymatrix_pia_result(inkey, pia0b): + col_row_values = _get_col_row_values(inkey, COCO_KEYMAP) result = _set_bits(pia0b, col_row_values) return result - -def test(char_or_code, matrix_name, auto_shift=False): +def inkey_from_tk_event(event, auto_shift=True): + if event.keysym_num>64000: # FIXME: Found a boundary number + inkey=event.keysym + else: + inkey=event.char + if auto_shift: + inkey = invert_shift(inkey) + return inkey + +def add_to_input_queue(user_input_queue, txt): + log.debug("Add %s to input queue.", repr(txt)) + txt=txt.replace("\r\n", "\r").replace("\n","\r") + for char in txt: + if char == "\r": + char="Return" # tkinter event.keysym string + log.debug("Add: %r", char) + user_input_queue.put(char) + +def test(inkey, matrix_name, auto_shift=False): """ >>> test("P", "dragon") char/keycode: 'P' -> cols/rows: ((0, 2),) @@ -255,20 +269,20 @@ def test(char_or_code, matrix_name, auto_shift=False): col col: 76543210 row -> 6543210 """ if matrix_name == "dragon": - col_row_values = _get_col_row_values(char_or_code, DRAGON_KEYMAP, auto_shift=auto_shift) + col_row_values = _get_col_row_values(inkey, DRAGON_KEYMAP) elif matrix_name == "coco": - col_row_values = _get_col_row_values(char_or_code, COCO_KEYMAP, auto_shift=auto_shift) + col_row_values = _get_col_row_values(inkey, COCO_KEYMAP) else: raise RuntimeError - print("char/keycode: %s -> cols/rows: %s" % (repr(char_or_code), repr(col_row_values))) + print("char/keycode: %s -> cols/rows: %s" % (repr(inkey), repr(col_row_values))) for i in xrange(8): pia0b = invert_byte(2 ** i) # written into $ff02 if matrix_name == "dragon": - result = get_dragon_keymatrix_pia_result(char_or_code, pia0b, auto_shift=auto_shift) # read from $ff00 + result = get_dragon_keymatrix_pia_result(inkey, pia0b) # read from $ff00 else: - result = get_coco_keymatrix_pia_result(char_or_code, pia0b, auto_shift=auto_shift) # read from $ff00 + result = get_coco_keymatrix_pia_result(inkey, pia0b) # read from $ff00 addr = 0x152 + i print("PB%i - $ff02 in $%02x (%s) -> $ff00 out $%02x (%s) stored in $%04x" % ( i, pia0b, '{0:08b}'.format(pia0b), @@ -308,16 +322,15 @@ def __init__(self): self.root.update() def event_key_pressed(self, event): - char_or_code = event.char or event.keycode - - print("Char: %s - keycode: %s - char_or_code: %s" % ( - repr(event.char), verbose_value(event.keycode), - repr(char_or_code) + print("event.char: %-6r event.keycode: %-3r event.keysym: %-11r event.keysym_num: %5r" % ( + event.char, event.keycode, event.keysym, event.keysym_num )) + inkey = inkey_from_tk_event(event, auto_shift=True) + print("inkey from event: %r" % inkey) try: - test(char_or_code, -# auto_shift=True - auto_shift=False + test(inkey, + matrix_name="dragon", + # matrix_name="coco", ) except: self.root.destroy() diff --git a/dragonpy/Dragon32/machine.py b/dragonpy/Dragon32/machine.py index a5a5156a..ee0ee577 100644 --- a/dragonpy/Dragon32/machine.py +++ b/dragonpy/Dragon32/machine.py @@ -12,7 +12,6 @@ from __future__ import absolute_import, division, print_function - import logging from dragonpy.Dragon32.config import Dragon32Cfg diff --git a/dragonpy/Dragon32/periphery_dragon.py b/dragonpy/Dragon32/periphery_dragon.py index 3d274cc4..b1a9b045 100644 --- a/dragonpy/Dragon32/periphery_dragon.py +++ b/dragonpy/Dragon32/periphery_dragon.py @@ -18,6 +18,7 @@ from __future__ import absolute_import, division, print_function import logging +from dragonpy.Dragon32.keyboard_map import add_to_input_queue log=logging.getLogger(__name__) from dragonpy.Dragon32.MC6821_PIA import PIA @@ -29,7 +30,6 @@ class Dragon32PeripheryBase(object): """ GUI independent stuff """ - def __init__(self, cfg, cpu, memory, user_input_queue): self.cfg = cfg self.cpu = cpu @@ -101,9 +101,8 @@ def setUp(self): self.display_buffer = {} # for striped_output() def add_to_input_queue(self, txt): - log.debug("Add %s to input queue.", repr(txt)) - for char in txt: - self.user_input_queue.put(char) + assert "\n" not in txt, "remove all \\n in unittests! Use only \\r as Enter!" + add_to_input_queue(self.user_input_queue, txt) def to_line_buffer(self, cpu_cycles, op_address, address, value): char, color = self.charmap[value] diff --git a/dragonpy/core/gui.py b/dragonpy/core/gui.py old mode 100644 new mode 100755 index 7808f9bc..f69167b4 --- a/dragonpy/core/gui.py +++ b/dragonpy/core/gui.py @@ -11,12 +11,15 @@ """ from __future__ import absolute_import, division, print_function +from dragonlib.utils import six +from dragonpy.Dragon32.keyboard_map import inkey_from_tk_event, add_to_input_queue + +xrange = six.moves.xrange import sys import time import logging import string -from dragonlib.utils.auto_shift import invert_shift try: # Python 3 @@ -116,6 +119,8 @@ def __init__(self, cfg, user_input_queue): helpmenu.add_command(label="about", command=self.menu_event_about) self.menubar.add_cascade(label="help", menu=helpmenu) + self.auto_shift=True # auto shift all input characters? + def init_statistics(self): self.op_count = 0 self.last_op_count = 0 @@ -187,8 +192,7 @@ def command_cpu_hard_reset(self): # ----------------------------------------------------------------------------------------- def add_user_input(self, txt): - for char in txt: - self.user_input_queue.put(char) + add_to_input_queue(self.user_input_queue, txt) def wait_until_input_queue_empty(self): for count in xrange(1, 10): @@ -217,8 +221,12 @@ def paste_clipboard(self, event): self.add_user_input(line + "\r") def event_key_pressed(self, event): - char_or_code = event.char or event.keycode - self.user_input_queue.put(char_or_code) + log.critical("event.char: %-6r event.keycode: %-3r event.keysym: %-11r event.keysym_num: %5r", + event.char, event.keycode, event.keysym, event.keysym_num + ) + inkey = inkey_from_tk_event(event, auto_shift=self.auto_shift) + log.critical("inkey: %r", inkey) + self.user_input_queue.put(inkey) total_burst_duration = 0 cpu_interval_calls = 0 @@ -353,19 +361,19 @@ def command_load_from_DragonPy(self): self.add_user_input_and_wait("'SAVE TO EDITOR") listing_ascii = self.machine.get_basic_program() self._editor_window.set_content(listing_ascii) - self.add_user_input_and_wait("\r") + self.add_user_input_and_wait("\n") def command_inject_into_DragonPy(self): self.add_user_input_and_wait("'LOAD FROM EDITOR") content = self._editor_window.get_content() result = self.machine.inject_basic_program(content) log.critical("program loaded: %s", result) - self.add_user_input_and_wait("\r") + self.add_user_input_and_wait("\n") def command_inject_and_run_into_DragonPy(self): self.command_inject_into_DragonPy() - self.add_user_input_and_wait("\r") # FIXME: Sometimes this input will be "ignored" - self.add_user_input_and_wait("RUN\r") + self.add_user_input_and_wait("\n") # FIXME: Sometimes this input will be "ignored" + self.add_user_input_and_wait("RUN\n") # ########################################################################## diff --git a/dragonpy/tests/test_BASIC_Dragon32.py b/dragonpy/tests/test_BASIC_Dragon32.py index bde34423..bff6450f 100644 --- a/dragonpy/tests/test_BASIC_Dragon32.py +++ b/dragonpy/tests/test_BASIC_Dragon32.py @@ -33,17 +33,17 @@ class Test_Dragon32_BASIC(Test6809_Dragon32_Base): # super(Test_Dragon32_BASIC, cls).setUpClass() def test_print01(self): - self.periphery.add_to_input_queue('? "FOO"\r\n') + self.periphery.add_to_input_queue('? "FOO"\r') op_call_count, cycles, output = self._run_until_OK(max_ops=57000) -# print op_call_count, cycles, output + print(op_call_count, cycles, output) self.assertEqual(output, ['? "FOO"', 'FOO', 'OK'] ) - self.assertEqual(op_call_count, 56137) - self.assertEqual(cycles, 316144) # TODO: cycles are probably not set corrent in CPU, yet! + self.assertEqual(op_call_count, 56143) + self.assertEqual(cycles, 316192) # TODO: cycles are probably not set corrent in CPU, yet! def test_poke(self): - self.periphery.add_to_input_queue('POKE &H05ff,88\r\n') + self.periphery.add_to_input_queue('POKE &H05ff,88\r') op_call_count, cycles, output = self._run_until_OK(max_ops=114000) # print op_call_count, cycles, output self.assertEqual(output, @@ -55,9 +55,9 @@ def test_code_load01(self): self.assertEqual(output, []) self.periphery.add_to_input_queue( - '10A=1\r\n' - '20B=2\r\n' - 'LIST\r\n' + '10A=1\r' + '20B=2\r' + 'LIST\r' ) op_call_count, cycles, output = self._run_until_OK(max_ops=143000) # print op_call_count, cycles, output @@ -77,7 +77,7 @@ def test_code_save01(self): ) # Check the lising - self.periphery.add_to_input_queue('LIST\r\n') + self.periphery.add_to_input_queue('LIST\r') op_call_count, cycles, output = self._run_until_OK(max_ops=4000000) # print op_call_count, cycles, output self.assertEqual(output, @@ -87,9 +87,9 @@ def test_code_save01(self): @unittest.expectedFailure # TODO: def test_tokens_in_string(self): self.periphery.add_to_input_queue( - # "10 PRINT ' FOR NEXT COMMENT\r\n" - "10 PRINT ' FOR NEXT\r\n" - 'LIST\r\n' + # "10 PRINT ' FOR NEXT COMMENT\r" + "10 PRINT ' FOR NEXT\r" + 'LIST\r' ) op_call_count, cycles, output = self._run_until_OK(max_ops=1430000) print(op_call_count, cycles, output) @@ -103,13 +103,13 @@ def test_tokens_in_string(self): if __name__ == '__main__': setup_logging( - # level=1 # hardcore debug ;) -# level=10 # DEBUG -# level=20 # INFO -# level=30 # WARNING - level=40 # ERROR -# level=50 # CRITICAL/FATAL -# level=99 # nearly off + # level=1 # hardcore debug ;) + # level=10 # DEBUG + # level=20 # INFO + # level=30 # WARNING + # level=40 # ERROR + level=50 # CRITICAL/FATAL + # level=99 # nearly off ) unittest.main( From 2b4e73597e3f5189dc1c0bc2c94279dbd86241aa Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Tue, 30 Sep 2014 15:47:18 +0200 Subject: [PATCH 142/151] add some alive "loading..." info --- BASIC games/INVADER.bas | 217 +++++++++++++++++++++++----------------- 1 file changed, 124 insertions(+), 93 deletions(-) diff --git a/BASIC games/INVADER.bas b/BASIC games/INVADER.bas index d9c1f9f7..1970fe7e 100755 --- a/BASIC games/INVADER.bas +++ b/BASIC games/INVADER.bas @@ -1,93 +1,124 @@ -10 GOSUB860:GOSUB810:CLS:CLEAR5000:DIML(511),Y(542),T(542),K(255),L$,B$,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,AK,BC,N1,LV,SC:GOTO500 -20 RETURN -30 FORU=I TO B:RESET(Y,K):K=K-I:ONPOINT(Y,K)GOTO60,60,60,40,40,40,40,270:SET(Y,K,I):NEXT:RESET(Y,K):RETURN -40 H=INT(K/W)*G+INT(Y/W):PRINT@H-I,CHR$(N2)CHR$(N2+RND(N7))CHR$(N2+RND(N7));:AK=AK+I:IFAK>N1 THENU=B:NEXT:L=E:RETURN -50 VP=VARPTR(L$):FORZ=-I TOI:POKE(PEEK(VP+W)*V)+PEEK(VP+R)+(H-L)+Z,N2:NEXT:SET(Y,K,I):U=B:K=O:NEXT:RETURN -60 RESET(Y,K):U=B:K=O:Y=O:NEXT:RESET(Y,K):RETURN -70 RESET(C,M):M=M+W:ONPOINT(C,M)GOTO240,150,150:SET(C,M,I):RETURN -80 T=R:NEXT:OND+I GOTO90,90:S=L:L=E:NEXT:D=I:F=O:E=N9:X=S1:GOTO100 -90 S=L:L=E:NEXT:D=-I:F=N1:E=N1:X=R:MS=MS+W:PRINT@MS,M$;:IFMS>N8 THENPRINT@MS,LEFT$(B$,5);:MS=O -100 BC=BC+I:IFBC>(G-AK)/W AND SB)+I GOTO20:P=P-W:Q=Q-I:RETURN -170 ON(PO AND AK=32 THEN390 -310 PRINT@MS,LEFT$(B$,6);:IFS<448 THEN FORL=S TO 479 STEP32:PRINT@L-N1,LEFT$(B$,L(L-N1));:PRINT@L,LEFT$(L$,L(L));:SOUND1,2:NEXT -320 PRINT@0, "YOU ARE DEAD. ";:SC=SC+(AK*LV):PRINT"SCORE=";SC:IFSC>HS THEN HS=SC -330 PRINT@32,"PLAY AGAIN (Y/N)? ";:PRINT"HIGH=";HS -340 IFAK<32 THEN PRINT@196,"THE ALIENS HAVE LANDED!";:SOUND100,15 -350 I$=INKEY$:IFI$=""THEN350 -360 IFI$="Y" THEN 680 -370 IFI$="N"THENGOSUB910:GOSUB840:END -380 GOTO350 -390 PRINT@0,"ANOTHER WAVE IS COMING!" -400 SOUND100,3:SOUND50,3:SOUND100,3:SOUND50,5:SOUND50,5:SOUND100,3:SOUND50,3 -410 SC=SC+(32*LV):PRINT@266,"SCORE=";SC;:FORT=1TO2000:NEXT -420 IFNP=SS THEN PRINT@198,"+100 SURVIVAL BONUS";:FORT=1 TO 2:FORZ=1 TO 24:READAA,BB:SOUNDAA,BB:NEXT:RESTORE:NEXT:SC=SC+100 -430 XM=XM+I:IFXM=2 THEN GOSUB450 -440 GOSUB700:GOTO110 -450 XM=0:IFNP=3 THEN 490 -460 PRINT@198,"*SHIELDS REPAIRED!*"; -470 NP=NP+1 -480 FORT=1 TO 10:POKE1535,48+NP+64:PRINT@Q,Q$;:FORZ=1 TO 200:NEXT:POKE1535,48+NP:PRINT@Q,P$;:FORZ=1 TO 200:NEXT:NEXT -490 RETURN -500 PRINT@231,"* SPACE INVADERS *"; -510 PRINT@291,"FOR THE MC-10 BY JIM GERRIE"; -520 PRINT@485,"PRESS ANY KEY TO BEGIN"; -530 P$=INKEY$:R=RND(1000):IFP$="" THEN 530 -540 CLS:PRINT"PLEASE WAIT..." -550 FORT=1TO32:B$=B$+CHR$(128):NEXT -560 O=0:I=1:W=2:R=3:B=4:G=32:N=29:V=256:N1=31 -570 K(65)=1:K(83)=2:K(32)=3:K(8)=1:K(9)=2:A=17023 -580 DIMN2,N3,N4,N5,N6,N7,N8,N9,MS,NS,XM,NP,SS,Q$,P$,I$,M$,V(3),VP,S1 -590 P$=CHR$(128)+CHR$(135+32)+CHR$(139+32)+CHR$(128):Q$=CHR$(128)+CHR$(135+16)+CHR$(139+16)+CHR$(128) -600 N2=128:N3=27:N4=58:N5=28:N6=480:N7=14:N8=24:N9=479:S1=61:SH$=CHR$(135+32)+CHR$(139+32) -610 FORT=0TO256:L(T)=224:NEXT:FORT=257TO479:L(T)=L(T-1)-1:NEXT -620 FORT=0TO286:T(T)=3:Y(T)=INT((T/16)+.1):NEXT -630 FORT=287TO350:T(T)=2:Y(T)=INT((T/16)+.1):NEXT -640 FORT=351TO414:T(T)=1:Y(T)=INT((T/16)+.1):NEXT -650 FORT=415TO478:T(T)=0:Y(T)=INT((T/16)+.1):NEXT -660 FORT=479TO542:T(T)=0:Y(T)=28:NEXT -670 FORT=0TO3:V(T)=B*T:NEXT:M$=CHR$(128)+CHR$(128)+CHR$(241)+CHR$(255)+CHR$(251) -680 SC=0:LV=0:NS=0:NP=3:MS=0:XM=0:GOSUB700 -690 GOTO110 -700 CLS0:D=1:E=479:X=61:V(0)=0:NS=NS+1:LV=LV+1:IFLV>4 THEN LV=4 -710 PRINT@480,NS;" ";:FORT=1504TO1508:POKET,PEEK(T)-64:NEXT:FORT=1509TO1535:POKET,G:NEXT:POKE1535,48+NP -720 FORT=1TO3:PRINT@348+(9*T),CHR$(135);CHR$(143);CHR$(139);:PRINT@348+(9*T)+32,CHR$(143);CHR$(143);CHR$(143);:NEXT -730 P=31:Q=448+14:M=1:L=LV*32:S=L:AK=0:BC=0:F=0:H=0:K=0:U=0:Z=0 -740 L$="":C=32:FORY=1TO4:C=C+16:FORT=1TO8:L$=L$+CHR$(128+C)+CHR$(133+C)+CHR$(141+C):NEXT:IFY=4 THEN L$=L$+LEFT$(B$,W):GOTO760 -750 L$=L$+B$+LEFT$(B$,8) -760 NEXT:C=0:SS=NP:RETURN -770 DATA 89,1,125,1,147,1,176,1,147,1,125,1 -780 DATA 89,1,125,1,147,1,89,1,147,1,125,1 -790 DATA 89,1,125,1,147,1,176,1,147,1,125,1 -800 DATA 89,1,125,1,147,1,89,1,147,1,125,1 -810 IF PEEK(65535)=27 THEN POKE65497,0:GOTO830 :ELSE CLS:INPUT"CAN YOUR COMPUTER HANDLE DOUBLE SPEED (Y/N)";A$ -820 IF A$="Y" THEN POKE65495,0 :ELSE IF A$<>"N" THEN810 -830 CLS:RETURN -840 IF PEEK(65535)=27 THEN POKE65496,0 :ELSE POKE65494,0 -850 RETURN -860 :' ENABLE DRAGON SPEEDKEY -870 IF PEEK(65535)<>180 THEN 890 -880 IF PEEK(269)+PEEK(270)<>1 THEN POKE65283,52:POKE256,116:POKE257,1:POKE258,81:POKE259,126:POKE260,PEEK(269):POKE 261,PEEK(270):POKE269,1:POKE270,0:POKE65283,53 -890 RETURN -900 :' DISABLE DRAGON SPEEDKEY -910 IF PEEK(65535)<>180 THEN 930 -920 IF PEEK(269)+PEEK(270)=1 THEN POKE65283,52:POKE269,PEEK(260):POKE270,PEEK(261):POKE65283,53 -930 RETURN \ No newline at end of file +10 'GOSUB1170:GOSUB1120 +20 CLS:PRINT "INIT [ ]": +30 CLEAR5000 +40 PRINT @6,"."; +50 DIM L(511) +60 PRINT"."; +70 DIM Y(542) +80 PRINT"."; +90 DIM T(542) +100 PRINT"."; +110 DIM K(255) +120 PRINT"."; +130 DIM L$,B$ +140 PRINT"."; +150 DIM A,B,C,D,E,F,G,H,I,J,K,L,M +160 PRINT"."; +170 DIM N,O,P,Q,R,S,T,U,V,W,X,Y,Z +180 PRINT"."; +190 DIM AK,BC,N1,LV,SC +200 GOTO690 +210 RETURN +220 FORU=I TO B:RESET(Y,K):K=K-I:ONPOINT(Y,K)GOTO250,250,250,230,230,230,230,460:SET(Y,K,I):NEXT:RESET(Y,K):RETURN +230 H=INT(K/W)*G+INT(Y/W):PRINT@H-I,CHR$(N2)CHR$(N2+RND(N7))CHR$(N2+RND(N7));:AK=AK+I:IFAK>N1 THENU=B:NEXT:L=E:RETURN +240 VP=VARPTR(L$):FORZ=-I TOI:POKE(PEEK(VP+W)*V)+PEEK(VP+R)+(H-L)+Z,N2:NEXT:SET(Y,K,I):U=B:K=O:NEXT:RETURN +250 RESET(Y,K):U=B:K=O:Y=O:NEXT:RESET(Y,K):RETURN +260 RESET(C,M):M=M+W:ONPOINT(C,M)GOTO430,340,340:SET(C,M,I):RETURN +270 T=R:NEXT:OND+I GOTO280,280:S=L:L=E:NEXT:D=I:F=O:E=N9:X=S1:GOTO290 +280 S=L:L=E:NEXT:D=-I:F=N1:E=N1:X=R:MS=MS+W:PRINT@MS,M$;:IFMS>N8 THENPRINT@MS,LEFT$(B$,5);:MS=O +290 BC=BC+I:IFBC>(G-AK)/W AND SB)+I GOTO210:P=P-W:Q=Q-I:RETURN +360 ON(PO AND AK=32 THEN580 +500 PRINT@MS,LEFT$(B$,6);:IFS<448 THEN FORL=S TO 479 STEP32:PRINT@L-N1,LEFT$(B$,L(L-N1));:PRINT@L,LEFT$(L$,L(L));:SOUND1,2:NEXT +510 PRINT@0, "YOU ARE DEAD. ";:SC=SC+(AK*LV):PRINT"SCORE=";SC:IFSC>HS THEN HS=SC +520 PRINT@32,"PLAY AGAIN (Y/N)? ";:PRINT"HIGH=";HS +530 IFAK<32 THEN PRINT@196,"THE ALIENS HAVE LANDED!";:SOUND100,15 +540 I$=INKEY$:IFI$=""THEN540 +550 IFI$="Y" THEN 990 +560 IFI$="N"THENGOSUB1220:GOSUB1150:END +570 GOTO540 +580 PRINT@0,"ANOTHER WAVE IS COMING!" +590 SOUND100,3:SOUND50,3:SOUND100,3:SOUND50,5:SOUND50,5:SOUND100,3:SOUND50,3 +600 SC=SC+(32*LV):PRINT@266,"SCORE=";SC;:FORT=1TO2000:NEXT +610 IFNP=SS THEN PRINT@198,"+100 SURVIVAL BONUS";:FORT=1 TO 2:FORZ=1 TO 24:READAA,BB:SOUNDAA,BB:NEXT:RESTORE:NEXT:SC=SC+100 +620 XM=XM+I:IFXM=2 THEN GOSUB640 +630 GOSUB1010:GOTO300 +640 XM=0:IFNP=3 THEN 680 +650 PRINT@198,"*SHIELDS REPAIRED!*"; +660 NP=NP+1 +670 FORT=1 TO 10:POKE1535,48+NP+64:PRINT@Q,Q$;:FORZ=1 TO 200:NEXT:POKE1535,48+NP:PRINT@Q,P$;:FORZ=1 TO 200:NEXT:NEXT +680 RETURN +690 CLS:PRINT@231,"* SPACE INVADERS *"; +700 PRINT@291,"FOR THE MC-10 BY JIM GERRIE"; +710 PRINT@485,"PRESS ANY KEY TO BEGIN"; +720 P$=INKEY$:R=RND(1000):IFP$="" THEN 720 +730 CLS:PRINT"PLEASE WAIT [ ]"; +740 FORT=1TO32:B$=B$+CHR$(128):NEXT +750 PRINT @13,"."; +760 O=0:I=1:W=2:R=3:B=4:G=32:N=29:V=256:N1=31 +770 K(65)=1:K(83)=2:K(32)=3:K(8)=1:K(9)=2:A=17023 +780 PRINT"."; +790 DIM N2,N3,N4,N5,N6,N7,N8,N9 +800 PRINT"."; +810 DIM MS,NS,XM,NP,SS,Q$,P$,I$,M$,V(3),VP,S1 +820 P$=CHR$(128)+CHR$(135+32)+CHR$(139+32)+CHR$(128):Q$=CHR$(128)+CHR$(135+16)+CHR$(139+16)+CHR$(128) +830 N2=128:N3=27:N4=58:N5=28:N6=480:N7=14:N8=24:N9=479:S1=61:SH$=CHR$(135+32)+CHR$(139+32) +840 PRINT"."; +850 FORT=0TO256:L(T)=224:NEXT:FORT=257TO479:L(T)=L(T-1)-1:NEXT +860 PRINT"."; +870 FORT=0TO286:T(T)=3:Y(T)=INT((T/16)+.1):NEXT +880 PRINT"."; +890 FORT=287TO350:T(T)=2:Y(T)=INT((T/16)+.1):NEXT +900 PRINT"."; +910 FORT=351TO414:T(T)=1:Y(T)=INT((T/16)+.1):NEXT +920 PRINT"."; +930 FORT=415TO478:T(T)=0:Y(T)=INT((T/16)+.1):NEXT +940 PRINT"."; +950 FORT=479TO542:T(T)=0:Y(T)=28:NEXT +960 PRINT"."; +970 FORT=0TO3:V(T)=B*T:NEXT:M$=CHR$(128)+CHR$(128)+CHR$(241)+CHR$(255)+CHR$(251) +980 PRINT"."; +990 SC=0:LV=0:NS=0:NP=3:MS=0:XM=0:GOSUB1010 +1000 GOTO300 +1010 CLS0:D=1:E=479:X=61:V(0)=0:NS=NS+1:LV=LV+1:IFLV>4 THEN LV=4 +1020 PRINT@480,NS;" ";:FORT=1504TO1508:POKET,PEEK(T)-64:NEXT:FORT=1509TO1535:POKET,G:NEXT:POKE1535,48+NP +1030 FORT=1TO3:PRINT@348+(9*T),CHR$(135);CHR$(143);CHR$(139);:PRINT@348+(9*T)+32,CHR$(143);CHR$(143);CHR$(143);:NEXT +1040 P=31:Q=448+14:M=1:L=LV*32:S=L:AK=0:BC=0:F=0:H=0:K=0:U=0:Z=0 +1050 L$="":C=32:FORY=1TO4:C=C+16:FORT=1TO8:L$=L$+CHR$(128+C)+CHR$(133+C)+CHR$(141+C):NEXT:IFY=4 THEN L$=L$+LEFT$(B$,W):GOTO1070 +1060 L$=L$+B$+LEFT$(B$,8) +1070 NEXT:C=0:SS=NP:RETURN +1080 DATA 89,1,125,1,147,1,176,1,147,1,125,1 +1090 DATA 89,1,125,1,147,1,89,1,147,1,125,1 +1100 DATA 89,1,125,1,147,1,176,1,147,1,125,1 +1110 DATA 89,1,125,1,147,1,89,1,147,1,125,1 +1120 IF PEEK(65535)=27 THEN POKE65497,0:GOTO1140 :ELSE CLS:INPUT"CAN YOUR COMPUTER HANDLE DOUBLE SPEED (Y/N)";A$ +1130 IF A$="Y" THEN POKE65495,0 :ELSE IF A$<>"N" THEN1120 +1140 CLS:RETURN +1150 IF PEEK(65535)=27 THEN POKE65496,0 :ELSE POKE65494,0 +1160 RETURN +1170 :' ENABLE DRAGON SPEEDKEY +1180 IF PEEK(65535)<>180 THEN 1200 +1190 IF PEEK(269)+PEEK(270)<>1 THEN POKE65283,52:POKE256,116:POKE257,1:POKE258,81:POKE259,126:POKE260,PEEK(269):POKE 261,PEEK(270):POKE269,1:POKE270,0:POKE65283,53 +1200 RETURN +1210 :' DISABLE DRAGON SPEEDKEY +1220 IF PEEK(65535)<>180 THEN 1240 +1230 IF PEEK(269)+PEEK(270)=1 THEN POKE65283,52:POKE269,PEEK(260):POKE270,PEEK(261):POKE65283,53 +1240 RETURN \ No newline at end of file From a9216f12d2c885f8afcdcf232bb07ffc7ade80af Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Tue, 30 Sep 2014 17:29:41 +0200 Subject: [PATCH 143/151] WIP: BASIC editor: reformat code --- basic_editor/editor.py | 14 +++++++- dragonlib/api.py | 71 +++++++++++++++++++++++++++++++++++------ dragonlib/core/basic.py | 24 ++++++++++++++ 3 files changed, 98 insertions(+), 11 deletions(-) diff --git a/basic_editor/editor.py b/basic_editor/editor.py index f196cddf..b49d5d73 100644 --- a/basic_editor/editor.py +++ b/basic_editor/editor.py @@ -104,6 +104,7 @@ def __init__(self, cfg, gui=None): editmenu = tkinter.Menu(self.menubar, tearoff=0) editmenu.add_command(label="renum", command=self.renumber_listing) + editmenu.add_command(label="reformat", command=self.reformat_listing) editmenu.add_command(label="display tokens", command=self.debug_display_tokens) self.menubar.add_cascade(label="tools", menu=editmenu) @@ -234,6 +235,18 @@ def renumber_listing(self): # restore text cursor and scroll position self.text.restore_position() + def reformat_listing(self): + # save text cursor and scroll position + self.text.save_position() + + # renumer the content + content = self.get_content() + content = self.machine_api.reformat_ascii_listing(content) + self.set_content(content) + + # restore text cursor and scroll position + self.text.restore_position() + def get_content(self): content = self.text.get("1.0", tkinter.END) content = content.strip() @@ -323,5 +336,4 @@ def test(): if __name__ == "__main__": - test() diff --git a/dragonlib/api.py b/dragonlib/api.py index 9413166e..686da65f 100644 --- a/dragonlib/api.py +++ b/dragonlib/api.py @@ -56,23 +56,28 @@ def parse_ascii_listing(self, basic_program_ascii): log.info("Parsed BASIC: %s", repr(parsed_lines)) return parsed_lines - def ascii_listing2program_dump(self, basic_program_ascii, program_start=None): - """ - convert a ASCII BASIC program listing into tokens. - This tokens list can be used to insert it into the - Emulator RAM. - """ + def ascii_listing2basic_lines(self, basic_program_ascii, program_start=None): if program_start is None: program_start = self.DEFAULT_PROGRAM_START - + parsed_lines = self.parse_ascii_listing(basic_program_ascii) - - basic_lines = [] + + basic_lines = [] for line_no, code_objects in sorted(parsed_lines.items()): basic_line = BasicLine(self.token_util) basic_line.code_objects_load(line_no,code_objects) basic_lines.append(basic_line) - + + return basic_lines + + def ascii_listing2program_dump(self, basic_program_ascii, program_start=None): + """ + convert a ASCII BASIC program listing into tokens. + This tokens list can be used to insert it into the + Emulator RAM. + """ + basic_lines = self.ascii_listing2basic_lines(basic_program_ascii, program_start) + return self.listing.basic_lines2program_dump(basic_lines, program_start) def pformat_tokens(self, tokens): @@ -94,6 +99,25 @@ def pformat_program_dump(self, program_dump, program_start=None): def renum_ascii_listing(self, content): return self.renum_tool.renum(content) + def reformat_ascii_listing(self, basic_program_ascii): + + parsed_lines = self.parse_ascii_listing(basic_program_ascii) + + ascii_lines = [] + for line_no, code_objects in sorted(parsed_lines.items()): + print() + print(line_no, code_objects) + basic_line = BasicLine(self.token_util) + basic_line.code_objects_load(line_no,code_objects) + + print(basic_line) + basic_line.reformat() + new_line = basic_line.get_content() + print(new_line) + ascii_lines.append(new_line) + + return "\n".join(ascii_lines) + class Dragon32API(BaseAPI): CONFIG_NAME = DRAGON32 @@ -117,3 +141,30 @@ class CoCoAPI(Dragon32API): MACHINE_NAME = "CoCo" BASIC_TOKENS = COCO_BASIC_TOKENS +if __name__ == '__main__': + import os + from dragonlib.utils.logging_utils import setup_logging + + setup_logging( +# level=1 # hardcore debug ;) +# level=10 # DEBUG +# level=20 # INFO +# level=30 # WARNING +# level=40 # ERROR +# level=50 # CRITICAL/FATAL + level=99 + ) + + api = Dragon32API() + + filepath = os.path.join(os.path.abspath(os.path.dirname(__file__)), + # "..", "BASIC examples", "hex_view01.bas" + "..", "BASIC games", "INVADER.bas" + ) + + with open(filepath, "r") as f: + listing_ascii = f.read() + + print( + api.reformat_ascii_listing(listing_ascii) + ) \ No newline at end of file diff --git a/dragonlib/core/basic.py b/dragonlib/core/basic.py index 30271f85..d03aafe6 100644 --- a/dragonlib/core/basic.py +++ b/dragonlib/core/basic.py @@ -209,6 +209,27 @@ def get_tokens(self): """ return list(word2bytes(self.line_number)) + self.line_code + def reformat(self): + space = self.token_util.ascii2token(" ")[0] + + temp1 = [] + temp2 = [] + for token in self.line_code: + temp1.append(token) + if token in self.token_util.basic_token_dict: + try: + if not temp2[-1]==space: + temp1.append(space) + except IndexError: + pass + temp2 += temp1 + temp1=[] + temp2 += temp1 + + self.line_code = temp2 + print("new line code: %r" % self.line_code) + + def get_content(self, code=None): if code is None: # start code = self.line_code @@ -218,6 +239,9 @@ def get_content(self, code=None): return line + def __repr__(self): + return "%r: %s" % (self.get_content(), " ".join(["$%02x" % t for t in self.line_code])) + def log_line(self): log.critical("%r:\n\t%s", self.get_content(), From 1433b7d8da9f09814adaa1717dfc4c30b36e3d4c Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Tue, 30 Sep 2014 20:21:51 +0200 Subject: [PATCH 144/151] Bugfix --- dragonlib/api.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dragonlib/api.py b/dragonlib/api.py index 686da65f..509faf93 100644 --- a/dragonlib/api.py +++ b/dragonlib/api.py @@ -56,10 +56,7 @@ def parse_ascii_listing(self, basic_program_ascii): log.info("Parsed BASIC: %s", repr(parsed_lines)) return parsed_lines - def ascii_listing2basic_lines(self, basic_program_ascii, program_start=None): - if program_start is None: - program_start = self.DEFAULT_PROGRAM_START - + def ascii_listing2basic_lines(self, basic_program_ascii, program_start): parsed_lines = self.parse_ascii_listing(basic_program_ascii) basic_lines = [] @@ -76,6 +73,9 @@ def ascii_listing2program_dump(self, basic_program_ascii, program_start=None): This tokens list can be used to insert it into the Emulator RAM. """ + if program_start is None: + program_start = self.DEFAULT_PROGRAM_START + basic_lines = self.ascii_listing2basic_lines(basic_program_ascii, program_start) return self.listing.basic_lines2program_dump(basic_lines, program_start) From ba396d15c5b1fe916017e263db27f07112107cf2 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Tue, 30 Sep 2014 22:32:37 +0200 Subject: [PATCH 145/151] add more info --- dragonlib/utils/iter_utils.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/dragonlib/utils/iter_utils.py b/dragonlib/utils/iter_utils.py index 7622f339..45088468 100644 --- a/dragonlib/utils/iter_utils.py +++ b/dragonlib/utils/iter_utils.py @@ -23,8 +23,8 @@ def list_replace(iterable, src, dst): >>> list_replace([1,2,3,4], (2,3), 9) [1, 9, 4] - >>> list_replace([1,2,3], (2,), "X") - [1, 'X', 3] + >>> list_replace([1,2,3], (2,), [9,8]) + [1, 9, 8, 3] >>> list_replace([1,2,3,4,5], (2,3,4), "X") [1, 'X', 5] @@ -37,6 +37,9 @@ def list_replace(iterable, src, dst): >>> list_replace([1,2,3,3,3,4,5], (3,3), "X") [1, 2, 'X', 3, 4, 5] + + >>> list_replace([1,2,3,3,3,4,5], (3,3), ("A","B","C")) + [1, 2, 'A', 'B', 'C', 3, 4, 5] >>> list_replace((58, 131, 73, 70), (58, 131), 131) [131, 73, 70] From 1d70ee928ad544740cb7b831160a9cfafd33224c Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Tue, 30 Sep 2014 22:44:42 +0200 Subject: [PATCH 146/151] WIP: BASIC editor: reformat code TODO: Use BASICParser to exclude string/comments etc. --- dragonlib/core/basic.py | 119 ++++++++++++++++++++++++++++++++-------- 1 file changed, 97 insertions(+), 22 deletions(-) diff --git a/dragonlib/core/basic.py b/dragonlib/core/basic.py index d03aafe6..a22ccddc 100644 --- a/dragonlib/core/basic.py +++ b/dragonlib/core/basic.py @@ -119,12 +119,7 @@ def code_objects2token(self, code_objects): tokens += self.chars2tokens(code_object.content) return tokens - def pformat_tokens(self, tokens): - """ - format a tokenized BASIC program line. Useful for debugging. - returns a list of formated string lines. - """ - result = [] + def iter_token_values(self, tokens): token_value = None for token in tokens: if token == 0xff: @@ -132,16 +127,23 @@ def pformat_tokens(self, tokens): continue if token_value is not None: - token_value = (token_value << 8) + token + yield (token_value << 8) + token + token_value = None else: - token_value = token + yield token + def pformat_tokens(self, tokens): + """ + format a tokenized BASIC program line. Useful for debugging. + returns a list of formated string lines. + """ + result = [] + for token_value in self.iter_token_values(tokens): char = self.token2ascii(token_value) if token_value > 0xff: result.append("\t$%04x -> %s" % (token_value, repr(char))) else: result.append("\t $%02x -> %s" % (token_value, repr(char))) - token_value = None return result @@ -210,24 +212,55 @@ def get_tokens(self): return list(word2bytes(self.line_number)) + self.line_code def reformat(self): + # TODO: Use BASICParser to exclude string/comments etc. space = self.token_util.ascii2token(" ")[0] - temp1 = [] - temp2 = [] - for token in self.line_code: - temp1.append(token) - if token in self.token_util.basic_token_dict: + to_split=self.token_util.basic_token_dict.copy() + dont_split_tokens=self.token_util.ascii2token(":()+-*/^<=>") + + for token_value in dont_split_tokens: + try: + del(to_split[token_value]) + except KeyError: # e.g.: () are not tokens + pass + + tokens=tuple(self.token_util.iter_token_values(self.line_code)) + + temp = [] + was_token=False + for no, token in enumerate(tokens): + try: + next_token=tokens[no+1] + except IndexError: + next_token=None + + if token in to_split: + print("X%sX" % to_split[token]) + try: - if not temp2[-1]==space: - temp1.append(space) + if temp[-1]!=space: + temp.append(space) except IndexError: pass - temp2 += temp1 - temp1=[] - temp2 += temp1 + temp.append(token) + + if not (next_token and next_token in dont_split_tokens): + temp.append(space) + was_token=True + else: + if was_token and token==space: + was_token=False + continue + print("Y%rY" % self.token_util.tokens2ascii([token])) + temp.append(token) - self.line_code = temp2 - print("new line code: %r" % self.line_code) + temp = list_replace(temp, self.token_util.ascii2token("GO TO"), self.token_util.ascii2token("GOTO")) + temp = list_replace(temp, self.token_util.ascii2token("GO SUB"), self.token_util.ascii2token("GOSUB")) + temp = list_replace(temp, self.token_util.ascii2token(": "), self.token_util.ascii2token(":")) + temp = list_replace(temp, self.token_util.ascii2token("( "), self.token_util.ascii2token("(")) + temp = list_replace(temp, self.token_util.ascii2token(", "), self.token_util.ascii2token(",")) + + self.line_code = temp def get_content(self, code=None): @@ -467,7 +500,8 @@ def create_renum_dict(self, ascii_listing): renum_dict[old_number] = new_number return renum_dict -if __name__ == "__main__": + +def _test_renum(): from dragonlib.api import Dragon32API api = Dragon32API() @@ -488,3 +522,44 @@ def create_renum_dict(self, ascii_listing): print("-" * 79) print(api.renum_tool.get_destinations(listing)) print("-" * 79) + + +def _test_reformat(): + import os + from dragonlib.utils.logging_utils import setup_logging + + setup_logging( +# level=1 # hardcore debug ;) +# level=10 # DEBUG +# level=20 # INFO +# level=30 # WARNING +# level=40 # ERROR +# level=50 # CRITICAL/FATAL + level=99 + ) + + from dragonlib.api import Dragon32API + api = Dragon32API() + + # filepath = os.path.join(os.path.abspath(os.path.dirname(__file__)), + # # "..", "BASIC examples", "hex_view01.bas" + # "..", "..", "BASIC games", "INVADER.bas" + # ) + # + # with open(filepath, "r") as f: + # listing_ascii = f.read() + + listing_ascii="""\ +10 ONPOINT(Y,K)GOTO250,250'ONPOINT(Y,K)GOTO250,250 +20 FORT=479TO 542:T(T)=0:Y(T)=28:NEXT +30 I=I+1:PRINT"FORX=1TO 2:Y(Y)=0:NEXT" +730 CLS:PRINT"FIXME: PLEASE WAIT [ ]"; +""" + + print( + api.reformat_ascii_listing(listing_ascii) + ) + +if __name__ == "__main__": + # _test_renum() + _test_reformat() From ecb1bc6f727312686893281604b2f473a28e2709 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Tue, 30 Sep 2014 22:45:13 +0200 Subject: [PATCH 147/151] Bugfix copy&paste: e.g.: double paste --- basic_editor/scrolled_text.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/basic_editor/scrolled_text.py b/basic_editor/scrolled_text.py index fb5f72ba..db7acfc0 100644 --- a/basic_editor/scrolled_text.py +++ b/basic_editor/scrolled_text.py @@ -69,12 +69,14 @@ def event_cut(self, event=None): if self.tag_ranges(tkinter.SEL): self.event_copy() self.delete(tkinter.SEL_FIRST, tkinter.SEL_LAST) + return "break" def event_copy(self, event=None): if self.tag_ranges(tkinter.SEL): text = self.get(tkinter.SEL_FIRST, tkinter.SEL_LAST) self.clipboard_clear() self.clipboard_append(text) + return "break" def event_paste(self, event=None): text = self.selection_get(selection='CLIPBOARD') @@ -82,6 +84,7 @@ def event_paste(self, event=None): self.insert(tkinter.INSERT, text) self.tag_remove(tkinter.SEL, '1.0', tkinter.END) self.see(tkinter.INSERT) + return "break" def __str__(self): return str(self.frame) From f874f837797f63799f8ad9fb121bc969d4d76041 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Wed, 8 Oct 2014 12:39:21 +0200 Subject: [PATCH 148/151] Add a setup.cfg --- setup.cfg | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 setup.cfg diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..8137ac4e --- /dev/null +++ b/setup.cfg @@ -0,0 +1,9 @@ +[flake8] +ignore=E115,E124,E128,E265,E301,E309,E501 +max-line-length = 119 + +[metadata] +license-file = LICENSE + +[bdist_wheel] +universal = 1 \ No newline at end of file From 737c6741a8eb5a8f171983dc90ee617fe58d3be9 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Wed, 8 Oct 2014 12:49:31 +0200 Subject: [PATCH 149/151] Release as v0.3.1 --- README.creole | 1 + dragonpy/__init__.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.creole b/README.creole index 86970dc7..35f42e4e 100644 --- a/README.creole +++ b/README.creole @@ -393,6 +393,7 @@ Six is a Python 2 and 3 compatibility library. == History +* 08.10.2014 - Release as v0.3.1 * 29.09.2014 - Merge [[https://github.com/jedie/PyDragon32|PyDragon32]] project * 25.09.2014 - Release v0.3.0: ** [[https://github.com/jedie/DragonPy/commit/f396551df730b509498d1b884cdda8f7075737c4|Change Display Queue to a simple Callback]] diff --git a/dragonpy/__init__.py b/dragonpy/__init__.py index 493f7415..260c070a 100644 --- a/dragonpy/__init__.py +++ b/dragonpy/__init__.py @@ -1 +1 @@ -__version__ = "0.3.0" +__version__ = "0.3.1" From 8b799963157787e9f01f5b0207e0f43d62da8893 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Wed, 8 Oct 2014 12:51:52 +0200 Subject: [PATCH 150/151] update README --- README.creole | 1 + 1 file changed, 1 insertion(+) diff --git a/README.creole b/README.creole index 35f42e4e..0a68cfc3 100644 --- a/README.creole +++ b/README.creole @@ -394,6 +394,7 @@ Six is a Python 2 and 3 compatibility library. == History * 08.10.2014 - Release as v0.3.1 +* 30.09.2014 - Enhance the BASIC editor * 29.09.2014 - Merge [[https://github.com/jedie/PyDragon32|PyDragon32]] project * 25.09.2014 - Release v0.3.0: ** [[https://github.com/jedie/DragonPy/commit/f396551df730b509498d1b884cdda8f7075737c4|Change Display Queue to a simple Callback]] From aa5b28d27abf9a32719233dfb807e88edc1e3ce3 Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Wed, 8 Oct 2014 12:54:27 +0200 Subject: [PATCH 151/151] Bugfix for ReSt --- README.creole | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.creole b/README.creole index 0a68cfc3..924cf881 100644 --- a/README.creole +++ b/README.creole @@ -421,6 +421,6 @@ Six is a Python 2 and 3 compatibility library. | PyPi | [[https://pypi.python.org/pypi/DragonPyEmulator/]] | Github | [[https://github.com/jedie/DragonPy]] -= donation +== donation * Send [[http://www.bitcoin.org/|Bitcoins]] to [[https://blockexplorer.com/address/1823RZ5Md1Q2X5aSXRC5LRPcYdveCiVX6F|1823RZ5Md1Q2X5aSXRC5LRPcYdveCiVX6F]]