sim.py 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. """
  2. gameduino.sim - simple simulator
  3. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  4. The Gameduino simulator can simulate some aspects of Gameduino hardware, both video and audio.
  5. It can be a useful tool for previewing media before loading on actual hardware.
  6. The Gameduino in this module is similar to the one in :mod:`gameduino.remote`::
  7. import gameduino
  8. import gameduino.prep as gdprep
  9. import gameduino.sim as gdsim
  10. im = Image.open("platformer.png").convert("RGB")
  11. (picd,chrd,pald) = gdprep.encode(im)
  12. gd = gdsim.Gameduino()
  13. gd.wrstr(gameduino.RAM_PIC, picd)
  14. gd.wrstr(gameduino.RAM_CHR, chrd)
  15. gd.wrstr(gameduino.RAM_PAL, pald)
  16. gd.im().save("preview.png")
  17. The simulator can produce screenshots (:meth:`Gameduino.im`), generate
  18. single-note sounds (:meth:`Gameduino.writewave`), and simulate collision RAM
  19. (:meth:`Gameduino.coll`). It does not currently simulate the coprocessor.
  20. """
  21. import struct
  22. import time
  23. import array
  24. import math
  25. import itertools
  26. import wave
  27. import binascii
  28. import Image
  29. from gameduino.registers import *
  30. from gameduino.base import BaseGameduino
  31. def sum512(a, b):
  32. return 511 & (a + b)
  33. def s9(vv):
  34. vv &= 0x1ff
  35. if vv > 400:
  36. vv -= 512;
  37. return vv
  38. class Gameduino(BaseGameduino):
  39. """
  40. The Gameduino object simulates some aspects of the Gameduino hardware. For example::
  41. >>> import gameduino
  42. >>> import gameduino.sim as gdsim
  43. >>> gd = gdsim.Gameduino()
  44. >>> print hex(gd.rd(gameduino.IDENT))
  45. 0x6d
  46. """
  47. def __init__(self):
  48. self.mem = array.array('B', [0] * 32768)
  49. self.mem[IDENT] = 0x6d
  50. self.coldstart()
  51. def wrstr(self, a, s):
  52. if not isinstance(s, str):
  53. s = s.tostring()
  54. self.mem[a:a+len(s)] = array.array('B', s)
  55. def rd(self, a):
  56. """ Read byte at address ``a`` """
  57. return self.mem[a]
  58. def rdstr(self, a, n):
  59. """
  60. Read ``n`` bytes starting at address ``a``
  61. :rtype: string of length ``n``.
  62. """
  63. return self.mem[a:a+n].tostring()
  64. def writewave(self, duration, dst):
  65. """
  66. Write the simulated output of the sound system to a wave file
  67. :param duration: length of clip in seconds
  68. :param dst: destination wave filename
  69. """
  70. sintab = [int(127 * math.sin(2 * math.pi * i / 128.)) for i in range(128)]
  71. nsamples = int(8000 * duration)
  72. master = [i/8000. for i in range(nsamples)]
  73. lacc = [0] * nsamples
  74. racc = [0] * nsamples
  75. for v in range(64):
  76. if v == self.rd(RING_START):
  77. print v, max(lacc)
  78. ring = [s/256 for s in lacc]
  79. lacc = [0] * nsamples
  80. racc = [0] * nsamples
  81. (freq,la,ra) = struct.unpack("<HBB", self.rdstr(VOICES + 4 * v, 4))
  82. if la or ra:
  83. tone = [sintab[int(m * freq * 32) & 0x7f] for m in master]
  84. if self.rd(RING_START) <= v < self.rd(RING_END):
  85. lacc = [o + la * (r * t) / 256 for (o,r,t) in zip(lacc, ring, tone)]
  86. racc = [o + ra * (r * t) / 256 for (o,r,t) in zip(racc, ring, tone)]
  87. else:
  88. lacc = [o + la * t for (o,t) in zip(lacc, tone)]
  89. racc = [o + ra * t for (o,t) in zip(racc, tone)]
  90. merged = [None,None] * nsamples
  91. merged[0::2] = lacc
  92. merged[1::2] = racc
  93. raw = array.array('h', merged)
  94. w = wave.open(dst, "wb")
  95. w.setnchannels(2)
  96. w.setsampwidth(2)
  97. w.setframerate(8000)
  98. w.writeframesraw(raw)
  99. w.close()
  100. def bg(self, lines = range(512)):
  101. bg_color = self.rd16(BG_COLOR) & 0x7fff
  102. glyphs = []
  103. for i in range(256):
  104. pals = array.array('H', self.mem[RAM_PAL + 8 * i:RAM_PAL + 8 * i + 8].tostring())
  105. for j in range(4):
  106. if pals[j] & 0x8000:
  107. pals[j] = bg_color
  108. glyph = []
  109. for y in range(8):
  110. for x in range(8):
  111. pix = 3 & (self.mem[RAM_CHR + 16 * i + 2 * y + (x / 4)] >> [6,4,2,0][x&3])
  112. glyph.append(pals[pix])
  113. glyphs.append(glyph)
  114. img = {}
  115. for y in lines:
  116. line = []
  117. for x in range(512):
  118. c = self.mem[RAM_PIC + 64 * (y >> 3) + (x >> 3)]
  119. line.append(glyphs[c][8 * (y & 7) + (x & 7)])
  120. img[y] = line
  121. return img
  122. def sprfetch(self, img, pal, rot, x, y):
  123. if rot & 1:
  124. exo,eyo = y,x
  125. else:
  126. exo,eyo = x,y
  127. if rot & 2:
  128. exo = 15 - exo
  129. if rot & 4:
  130. eyo = 15 - eyo
  131. ix = self.rd(RAM_SPRIMG + 256 * img + 16 * eyo + exo)
  132. if (pal & 0xc) == 0:
  133. pix = self.rd16(RAM_SPRPAL + 512 * (pal & 3) + 2 * ix)
  134. elif (pal & 0xc) == 4:
  135. nyb = 15 & (ix >> [0,4][1 & (pal >> 1)])
  136. pix = self.rd16(PALETTE16A + 32 * (pal & 1) + 2 * nyb)
  137. else:
  138. nyb = 3 & (ix >> [0,2,4,6][3 & (pal >> 1)])
  139. pix = self.rd16(PALETTE4A + 8 * (pal & 1) + 2 * nyb)
  140. return pix
  141. def spr_page(self):
  142. return RAM_SPR + 1024 * (self.rd(SPR_PAGE) & 1)
  143. def sp(self, y, line):
  144. if self.rd(SPR_DISABLE) & 1:
  145. return line
  146. page = self.spr_page()
  147. for i in range(256):
  148. sprval = self.rd32(page + 4 * i)
  149. sx = s9(sprval)
  150. sy = s9(sprval >> 16)
  151. simg = (sprval >> 25) & 63
  152. spal = (sprval >> 12) & 15
  153. srot = (sprval >> 9) & 7
  154. yo = y - sy
  155. if 0 <= yo < 16:
  156. for xo in range(16):
  157. if 0 <= (sx + xo) < 400:
  158. pix = self.sprfetch(simg, spal, srot, xo, yo)
  159. if pix < 32768:
  160. line[sx + xo] = pix
  161. return line
  162. def coll(self):
  163. """
  164. Return the 256 bytes of COLLISION RAM.
  165. :rtype: list of byte values.
  166. """
  167. coll = 256 * [0xff]
  168. page = self.spr_page()
  169. jkmode = (self.rd(JK_MODE) & 1) != 0
  170. if 0 == (self.rd(SPR_DISABLE) & 1):
  171. yocc = [[] for i in range(300)]
  172. for i in range(256):
  173. sprval = self.rd32(page + 4 * i)
  174. sy = s9(sprval >> 16)
  175. for j in range(16):
  176. if 0 <= (sy + j) < 300:
  177. yocc[sy + j].append(i)
  178. for y in range(300):
  179. tag = [None] * 400
  180. jk = [None] * 400
  181. for i in yocc[y]:
  182. sprval = self.rd32(page + 4 * i)
  183. sy = s9(sprval >> 16)
  184. yo = y - sy
  185. if 0 <= yo < 16:
  186. sx = s9(sprval)
  187. simg = (sprval >> 25) & 63
  188. spal = (sprval >> 12) & 15
  189. srot = (sprval >> 9) & 7
  190. sjk = (sprval >> 31)
  191. for xo in range(16):
  192. x = sx + xo
  193. if 0 <= x < 400:
  194. if self.sprfetch(simg, spal, srot, xo, yo) < 32768:
  195. if tag[x] != None:
  196. if (not jkmode) or (jk[x] != sjk):
  197. coll[i] = tag[x]
  198. tag[x] = i
  199. jk[x] = sjk
  200. return coll
  201. def screen(self, lines, w = 400):
  202. sx = self.rd16(SCROLL_X) & 511
  203. sy = self.rd16(SCROLL_Y)
  204. bg = self.bg([sum512(y, sy) for y in lines])
  205. def wrapx(l):
  206. return (l + l)[sx:sx+w]
  207. return dict([(y, self.sp(y, wrapx(bg[sum512(y, sy)]))) for y in lines])
  208. def _im(self):
  209. return self._imwh(400, 300)
  210. def fullim(self):
  211. """ Return the entire 512x512 pixel screen image """
  212. return _imwh(512, 512)
  213. def _imwh(self, w, h):
  214. import Image
  215. fi = Image.new("RGB", (w, h))
  216. lines = self.screen(range(h), w)
  217. for y in range(h):
  218. ld = lines[y]
  219. r = [8 * (31 & (v >> 10)) for v in ld]
  220. g = [8 * (31 & (v >> 5)) for v in ld]
  221. b = [8 * (31 & (v >> 0)) for v in ld]
  222. rgb = sum(zip(r,g,b), ())
  223. li = Image.fromstring("RGB", (w,1), array.array('B', rgb).tostring())
  224. fi.paste(li, (0, y))
  225. return fi
  226. def linecrc(self, y):
  227. line = array.array('H', self.screen([y])[y]).tostring()
  228. return 0xffffffff & binascii.crc32(line)
  229. def collcrc(self):
  230. return 0xffffffff & binascii.crc32(array.array('B', self.coll()).tostring())
  231. def memcrc(self, a, s):
  232. return 0xffffffff & binascii.crc32(self.mem[a:a+s])
  233. def memory(self):
  234. """ Returns current image of memory as a 32768 byte string """
  235. return self.mem.tostring()
  236. def readarray(filename):
  237. return array.array('B', open(filename).read())
  238. __all__ = [ "Gameduino" ]