1.1 --- a/ula.py Sun Dec 04 01:17:40 2011 +0100
1.2 +++ b/ula.py Sun Dec 04 21:43:45 2011 +0100
1.3 @@ -17,114 +17,171 @@
1.4 MAX_MEMORY = 0x10000
1.5 INTENSITY = 255
1.6
1.7 -palette = range(0, 8) * 2
1.8 -
1.9 -def get_mode((width, depth, rows)):
1.10 +def update(screen, ula):
1.11
1.12 """
1.13 - Return the 'width', 'depth', 'rows', calculated character row size in
1.14 - bytes, screen size in bytes, horizontal pixel scaling, vertical pixel
1.15 - spacing, and line spacing between character rows, as elements of a tuple for
1.16 - a particular screen mode.
1.17 - """
1.18 -
1.19 - return (width, depth, rows,
1.20 - (width * depth * LINES_PER_ROW) / 8, # bits per row -> bytes per row
1.21 - (width * depth * MAX_HEIGHT) / 8, # bits per screen -> bytes per screen
1.22 - WIDTH / width, # pixel width in display pixels
1.23 - HEIGHT / (rows * LINES_PER_ROW), # pixel height in display pixels
1.24 - MAX_HEIGHT / rows - LINES_PER_ROW) # pixels between rows
1.25 -
1.26 -modes = map(get_mode, [
1.27 - (640, 1, 32), (320, 2, 32), (160, 4, 32), # (width, depth, rows)
1.28 - (640, 1, 24), (320, 1, 32), (160, 2, 32),
1.29 - (320, 1, 24)
1.30 - ])
1.31 -
1.32 -def update(screen, memory, start, mode):
1.33 -
1.34 - """
1.35 - Update the 'screen' array by reading from 'memory' at the given 'start'
1.36 - address for the given 'mode'.
1.37 + Update the 'screen' array by reading from the 'ula'.
1.38 """
1.39
1.40 - # Get the width in pixels, colour depth in bits per pixel, number of
1.41 - # character rows, character row size in bytes, screen size in bytes,
1.42 - # horizontal pixel scaling factor, vertical pixel scaling factor, and line
1.43 - # spacing in pixels.
1.44 + ula.vsync()
1.45 + y = 0
1.46 + while y < HEIGHT:
1.47 + x = 0
1.48 + while x < WIDTH:
1.49 + colour = ula.get_pixel_colour()
1.50 + pixel = tuple(map(lambda x: x * INTENSITY, colour))
1.51 + screen[x][y] = pixel
1.52 + x += 1
1.53 + ula.hsync()
1.54 + y += 1
1.55
1.56 - width, depth, rows, row_size, screen_size, xscale, yscale, spacing = modes[mode]
1.57 +class ULA:
1.58 +
1.59 + "The ULA functionality."
1.60
1.61 - address = start
1.62 - y = 0
1.63 - row = 0
1.64 + modes = [
1.65 + (640, 1, 32), (320, 2, 32), (160, 4, 32), # (width, depth, rows)
1.66 + (640, 1, 24), (320, 1, 32), (160, 2, 32),
1.67 + (320, 1, 24)
1.68 + ]
1.69 +
1.70 + palette = range(0, 8) * 2
1.71 +
1.72 + def __init__(self, memory):
1.73
1.74 - while row < rows:
1.75 - row_start = address
1.76 - line = 0
1.77 + "Initialise the ULA with the given 'memory'."
1.78 +
1.79 + self.memory = memory
1.80 + self.set_mode(6)
1.81
1.82 - # Emit each character row.
1.83 + # Internal state.
1.84 +
1.85 + self.buffer = [0] * 8
1.86 +
1.87 + def set_mode(self, mode):
1.88
1.89 - while line < LINES_PER_ROW:
1.90 - line_start = address
1.91 - ysub = 0
1.92 + """
1.93 + For the given 'mode', initialise the...
1.94
1.95 - # Scale each row of pixels vertically.
1.96 + * width in pixels
1.97 + * colour depth in bits per pixel
1.98 + * number of character rows
1.99 + * character row size in bytes
1.100 + * screen size in bytes
1.101 + * default screen start address
1.102 + * horizontal pixel scaling factor
1.103 + * vertical pixel scaling factor
1.104 + * line spacing in pixels
1.105 + * number of entries in the pixel buffer
1.106 + """
1.107
1.108 - while ysub < yscale:
1.109 - x = 0
1.110 + self.width, self.depth, self.rows = self.modes[mode]
1.111 +
1.112 + row_size = (self.width * self.depth * LINES_PER_ROW) / 8 # bits per row -> bytes per row
1.113
1.114 - # Emit each row of pixels.
1.115 + self.screen_size = row_size * self.rows
1.116 + self.screen_start = SCREEN_LIMIT - self.screen_size
1.117 + self.xscale = WIDTH / self.width # pixel width in display pixels
1.118 + self.yscale = HEIGHT / (self.rows * LINES_PER_ROW) # pixel height in display pixels
1.119 + self.spacing = MAX_HEIGHT / self.rows - LINES_PER_ROW # pixels between rows
1.120 + self.buffer_limit = 8 / self.depth
1.121
1.122 - while x < WIDTH:
1.123 - byte_value = memory[address]
1.124 + def vsync(self):
1.125 +
1.126 + "Signal the start of a frame."
1.127
1.128 - for colour in decode(byte_value, depth):
1.129 - colour = get_physical_colour(palette[colour])
1.130 - pixel = tuple(map(lambda x: x * INTENSITY, colour))
1.131 + self.line_start = self.address = self.screen_start
1.132 + self.line = 0
1.133 + self.ysub = 0
1.134 + self.reset_horizontal()
1.135
1.136 - # Scale the pixels horizontally.
1.137 + def reset_horizontal(self):
1.138 +
1.139 + "Reset horizontal state."
1.140 +
1.141 + self.xsub = 0
1.142 + self.buffer_index = self.buffer_limit # need refill
1.143
1.144 - xsub = 0
1.145 - while xsub < xscale:
1.146 - screen[x][y] = pixel
1.147 - xsub += 1
1.148 - x += 1
1.149 + def hsync(self):
1.150 +
1.151 + "Signal the end of a line."
1.152 +
1.153 + self.reset_horizontal()
1.154 +
1.155 + # Scale pixels vertically.
1.156
1.157 - # Advance to the next column.
1.158 + self.ysub += 1
1.159 +
1.160 + # Re-read the current line if appropriate.
1.161 +
1.162 + if self.ysub < self.yscale:
1.163 + self.address = self.line_start
1.164 + return
1.165 +
1.166 + # Otherwise, move on to the next line.
1.167
1.168 - address += LINES_PER_ROW
1.169 - address = wrap_address(address, screen_size)
1.170 + self.ysub = 0
1.171 + self.line += 1
1.172 +
1.173 + if self.line < LINES_PER_ROW:
1.174 + self.address = self.line_start + 1
1.175 + self.wrap_address()
1.176 +
1.177 + # After the end of the last line in a row, the address should already
1.178 + # have been positioned on the last line of the next column.
1.179
1.180 - ysub += 1
1.181 - y += 1
1.182 + else:
1.183 + self.address -= LINES_PER_ROW - 1
1.184 + self.wrap_address()
1.185
1.186 - # Return to the address at the start of the line in order to be
1.187 - # able to repeat the line.
1.188 + self.line_start = self.address
1.189
1.190 - address = line_start
1.191 + # Move on to the next row if appropriate.
1.192 +
1.193 + if self.line == LINES_PER_ROW:
1.194 + self.line = 0
1.195
1.196 - # Move on to the next line in the row.
1.197 + def get_pixel_colour(self):
1.198
1.199 - line += 1
1.200 - address += 1
1.201 + """
1.202 + Return a pixel colour by reading from the pixel buffer.
1.203 + """
1.204 +
1.205 + # Scale pixels horizontally.
1.206
1.207 - # Skip spacing between rows.
1.208 + if self.xsub == self.xscale:
1.209 + self.xsub = 0
1.210 + self.buffer_index += 1
1.211
1.212 - if spacing:
1.213 - y += spacing * yscale
1.214 + if self.buffer_index == self.buffer_limit:
1.215 + self.buffer_index = 0
1.216 + self.fill_pixel_buffer()
1.217 +
1.218 + self.xsub += 1
1.219 + return self.buffer[self.buffer_index]
1.220 +
1.221 + def fill_pixel_buffer(self):
1.222
1.223 - # Move on to the next row.
1.224 + """
1.225 + Fill the pixel buffer by translating memory content for the current
1.226 + mode.
1.227 + """
1.228
1.229 - address = row_start + row_size
1.230 - address = wrap_address(address, screen_size)
1.231 + byte_value = self.memory[self.address]
1.232
1.233 - row += 1
1.234 + i = 0
1.235 + for colour in decode(byte_value, self.depth):
1.236 + self.buffer[i] = get_physical_colour(self.palette[colour])
1.237 + i += 1
1.238 +
1.239 + # Advance to the next column.
1.240
1.241 -def wrap_address(address, screen_size):
1.242 - if address >= SCREEN_LIMIT:
1.243 - address -= screen_size
1.244 - return address
1.245 + self.address += LINES_PER_ROW
1.246 + self.wrap_address()
1.247 +
1.248 + def wrap_address(self):
1.249 + if self.address >= SCREEN_LIMIT:
1.250 + self.address -= self.screen_size
1.251
1.252 def get_physical_colour(value):
1.253
1.254 @@ -196,10 +253,19 @@
1.255 memory = array.array("B", itertools.repeat(0, MAX_MEMORY))
1.256 a = pygame.surfarray.pixels3d(screen)
1.257
1.258 + ula = ULA(memory)
1.259 +
1.260 # Test MODE 2.
1.261
1.262 - fill(memory, 0x3000, 0x8000, encode((1, 6), 4))
1.263 - update(a, memory, 0x3000, 2)
1.264 + ula.set_mode(2)
1.265 +
1.266 + fill(memory, 0x3000, 0x5800 - 320, encode((1, 6), 4))
1.267 + fill(memory, 0x5800 - 320, 0x8000, encode((2, 7), 4))
1.268 + update(a, ula)
1.269 + mainloop()
1.270 +
1.271 + ula.screen_start = 0x5800 - 320
1.272 + update(a, ula)
1.273 mainloop()
1.274
1.275 # vim: tabstop=4 expandtab shiftwidth=4