ULA

ula.py

1:d6b8cd0ac739
2011-12-04 Paul Boddie Added a module which simulates the video functions of the Acorn Electron ULA.
     1 #!/usr/bin/env python     2      3 """     4 Acorn Electron ULA simulation.     5 """     6      7 import pygame     8 import array     9 import itertools    10     11 WIDTH = 640    12 HEIGHT = 512    13     14 LINES_PER_ROW = 8    15 MAX_HEIGHT = 256    16 SCREEN_LIMIT = 0x8000    17 MAX_MEMORY = 0x10000    18 INTENSITY = 255    19     20 palette = range(0, 8) * 2    21     22 def get_mode((width, depth, rows)):    23     24     """    25     Return the 'width', 'depth', 'rows', calculated character row size in    26     bytes, screen size in bytes, horizontal pixel scaling, vertical pixel    27     spacing, and line spacing between character rows, as elements of a tuple for    28     a particular screen mode.    29     """    30     31     return (width, depth, rows,    32         (width * depth * LINES_PER_ROW) / 8, # bits per row -> bytes per row    33         (width * depth * MAX_HEIGHT) / 8,    # bits per screen -> bytes per screen    34         WIDTH / width,                       # pixel width in display pixels    35         HEIGHT / (rows * LINES_PER_ROW),     # pixel height in display pixels    36         MAX_HEIGHT / rows - LINES_PER_ROW)   # pixels between rows    37     38 modes = map(get_mode, [    39     (640, 1, 32), (320, 2, 32), (160, 4, 32),   # (width, depth, rows)    40     (640, 1, 24), (320, 1, 32), (160, 2, 32),    41     (320, 1, 24)    42     ])    43     44 def update(screen, memory, start, mode):    45     46     """    47     Update the 'screen' array by reading from 'memory' at the given 'start'    48     address for the given 'mode'.    49     """    50     51     # Get the width in pixels, colour depth in bits per pixel, number of    52     # character rows, character row size in bytes, screen size in bytes,    53     # horizontal pixel scaling factor, vertical pixel scaling factor, and line    54     # spacing in pixels.    55     56     width, depth, rows, row_size, screen_size, xscale, yscale, spacing = modes[mode]    57     58     address = start    59     y = 0    60     row = 0    61     62     while row < rows:    63         row_start = address    64         line = 0    65     66         # Emit each character row.    67     68         while line < LINES_PER_ROW:    69             line_start = address    70             ysub = 0    71     72             # Scale each row of pixels vertically.    73     74             while ysub < yscale:    75                 x = 0    76     77                 # Emit each row of pixels.    78     79                 while x < WIDTH:    80                     byte_value = memory[address]    81     82                     for colour in decode(byte_value, depth):    83                         colour = get_physical_colour(palette[colour])    84                         pixel = tuple(map(lambda x: x * INTENSITY, colour))    85     86                         # Scale the pixels horizontally.    87     88                         xsub = 0    89                         while xsub < xscale:    90                             screen[x][y] = pixel    91                             xsub += 1    92                             x += 1    93     94                     # Advance to the next column.    95     96                     address += LINES_PER_ROW    97                     address = wrap_address(address, screen_size)    98     99                 ysub += 1   100                 y += 1   101    102                 # Return to the address at the start of the line in order to be   103                 # able to repeat the line.   104    105                 address = line_start   106    107             # Move on to the next line in the row.   108    109             line += 1   110             address += 1   111    112         # Skip spacing between rows.   113    114         if spacing:   115             y += spacing * yscale   116    117         # Move on to the next row.   118    119         address = row_start + row_size   120         address = wrap_address(address, screen_size)   121    122         row += 1   123    124 def wrap_address(address, screen_size):   125     if address >= SCREEN_LIMIT:   126         address -= screen_size   127     return address   128    129 def get_physical_colour(value):   130    131     """   132     Return the physical colour as an RGB triple for the given 'value'.   133     """   134    135     return value & 1, value >> 1 & 1, value >> 2 & 1   136    137 def decode(value, depth):   138    139     """   140     Decode the given byte 'value' according to the 'depth' in bits per pixel,   141     returning a sequence of pixel values.   142     """   143    144     if depth == 1:   145         return (value >> 7, value >> 6 & 1, value >> 5 & 1, value >> 4 & 1,   146                 value >> 3 & 1, value >> 2 & 1, value >> 1 & 1, value & 1)   147     elif depth == 2:   148         return (value >> 6 & 2 | value >> 3 & 1, value >> 5 & 2 | value >> 2 & 1,   149                 value >> 4 & 2 | value >> 1 & 1, value >> 3 & 2 | value & 1)   150     elif depth == 4:   151         return (value >> 4 & 8 | value >> 3 & 4 | value >> 2 & 2 | value >> 1 & 1,   152                 value >> 3 & 8 | value >> 2 & 4 | value >> 1 & 2 | value & 1)   153     else:   154         raise ValueError, "Only depths of 1, 2 and 4 are supported, not %d." % depth   155    156 # Convenience functions.   157    158 def encode(values, depth):   159    160     """   161     Encode the given 'values' according to the 'depth' in bits per pixel,   162     returning a byte value for the pixels.   163     """   164    165     result = 0   166    167     if depth == 1:   168         for value in values:   169             result = result << 1 | (value & 1)   170     elif depth == 2:   171         for value in values:   172             result = result << 1 | (value & 2) << 3 | (value & 1)   173     elif depth == 4:   174         for value in values:   175             result = result << 1 | (value & 8) << 3 | (value & 4) << 2 | (value & 2) << 1 | (value & 1)   176     else:   177         raise ValueError, "Only depths of 1, 2 and 4 are supported, not %d." % depth   178    179     return result   180    181 def fill(memory, start, end, value):   182     for i in xrange(start, end):   183         memory[i] = value   184    185 def mainloop():   186     while 1:   187         pygame.display.flip()   188         event = pygame.event.wait()   189         if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:   190             break   191    192 if __name__ == "__main__":   193     pygame.init()   194     screen = pygame.display.set_mode((WIDTH, HEIGHT), 0)   195    196     memory = array.array("B", itertools.repeat(0, MAX_MEMORY))   197     a = pygame.surfarray.pixels3d(screen)   198    199     # Test MODE 2.   200    201     fill(memory, 0x3000, 0x8000, encode((1, 6), 4))   202     update(a, memory, 0x3000, 2)   203     mainloop()   204    205 # vim: tabstop=4 expandtab shiftwidth=4