16 # target will yield this much time whenever it polls HW_IOADR
23 # see https://www.tinaja.com/ebooks/tearing_rework.pdf
24 ZP_WNDLFT = 0x20 # Left side of scroll window
25 ZP_WNDWTH = 0x21 # Width of scroll window
26 ZP_WNDTOP = 0x22 # Top of scroll window
27 ZP_WNDBTM = 0x23 # Bottom of scroll window
28 ZP_CH = 0x24 # Cursor horizontal position
29 ZP_CV = 0x25 # Cursor vertical position
30 ZP_GBASL = 0x21 # LORES graphics base low
31 ZP_GBASH = 0x27 # LORES graphics base high
32 ZP_BASL = 0x28 # TEXT base address low
33 ZP_BASH = 0x29 # TEXT base address high
34 ZP_BAS2L = 0x2A # Scroll temporary base low
35 ZP_BAS2H = 0x2B # Scroll temporary base high
36 ZP_COLOR = 0x30 # Holds the LORES color value
37 ZP_INVFLG = 0x32 # Normal/Inverse/Flash mask
38 ZP_PROMPT = 0x33 # Holds prompt symbol
39 ZP_YSAV = 0x34 # Temporary Y register hold
40 ZP_CSWL = 0x36 # Output character hook low
41 ZP_CSWH = 0x37 # Output character hook high
42 ZP_KSWL = 0x38 # Input character hook low
43 ZP_KSWH = 0x39 # Input character hook high
44 ZP_ACC = 0x45 # Accumulator save
45 ZP_XREG = 0x46 # X register save
46 ZP_YREG = 0x47 # Y register save
47 ZP_STATUS = 0x48 # Flag register save
48 ZP_SPNT = 0x49 # Stack pointer save
49 ZP_RNDL = 0x4E # Keybounce random number low
50 ZP_RNDH = 0x4F # Keybounce random number high
51 ZP_LOMEM = 0x69 # current lomem (word variable)
52 ZP_HIMEM = 0x73 # current himem (word variable)
53 VP_REENTER = 0x03D0 # Re-enter DOS
54 VP_RECONNECT = 0x03EA # Reconnect DOS I/O hooks
55 VP_BRKV = 0x03F0 # Break vector address
56 VP_SOFTEV = 0x03F2 # Warm start vector address
57 VP_PWRDUP = 0x03F4 # Warm start EOR A5 checksum
58 VP_AMPERV = 0x03F6 # Applesoft "&" Jump Code
59 VP_USRADR = 0x03F8 # Control Y vector Jump Code
60 VP_NMI = 0x03FB # NMI vector Jump Code
61 VP_IRQLOC = 0x03FE # Interrupt vector address
62 HW_IOADR = 0xC000 # Keyboard input location
63 HW_KBDSTRB = 0xC010 # Keyboard strobe reset
64 HW_TAPEOUT = 0xC020 # Cassette data output
65 HW_SPKR = 0xC030 # Speaker click output
66 HW_STROBE = 0xC040 # Game I/O connector strobe
67 HW_TXTCLR = 0xC050 # Graphics ON soft switch
68 HW_TXTSET = 0xC051 # Text ON soft switch
69 HW_MIXCLR = 0xC052 # Full screen ON soft switch
70 HW_MIXSET = 0xC053 # Split screen ON soft switch
71 HW_LOWSCR = 0xC054 # Page ONE display soft switch
72 HW_HISCR = 0xC055 # Page TWO display soft switch
73 HW_LORES = 0xC056 # LORES ON soft switch
74 HW_HIRES = 0xC057 # HIRES ON soft switch
75 HW_AN0CLR = 0xC058 # Annunciator 0 OFF soft switch
76 HW_AN0SET = 0xC059 # Annunciator 0 ON soft switch
77 HW_AN1CLR = 0xC05A # Annunciator 1 OFF soft switch
78 HW_AN1SET = 0xC05B # Annunciator 1 ON soft switch
79 HW_AN2CLR = 0xC05C # Annunciator 2 OFF soft switch
80 HW_AN2SET = 0xC05D # Annunciator 2 ON soft switch
81 HW_AN3CLR = 0xC05E # Annunciator 3 OFF soft switch
82 HW_AN3SET = 0xC05F # Annunciator 3 ON soft switch
83 HW_TAPEIN = 0xC060 # Cassette tape read input
84 HW_PB0 = 0xC061 # Push button 0 input
85 HW_PB1 = 0xC062 # Push button 1 input
86 HW_PB2 = 0xC063 # Push button 2 input
87 HW_PDL0 = 0xC064 # Game Paddle 0 analog input
88 HW_PDL1 = 0xC065 # Game Paddle 1 analog input
89 HW_PDL2 = 0xC066 # Game Paddle 2 analog input
90 HW_PDL3 = 0xC067 # Game Paddle 3 analog input
91 ROM_PLOT = 0xF800 # Plot a block on LORES screen
92 ROM_HLINE = 0xF819 # Draw a horizontal LORES line
93 ROM_VLINE = 0xF828 # Draw a vertical LORES line
94 ROM_CLRSCR = 0xF832 # Clear full LORES screen
95 ROM_CLRTOP = 0xF836 # Clear top of LORES screen
96 ROM_GBASCALC = 0xF847 # Calculate LORES base address
97 ROM_NEXTCOL = 0xF85F # Increase LORES color by three
98 ROM_SETCOL = 0xF864 # Set color for LORES plotting
99 ROM_SCRN = 0xF871 # Read color of LORES screen
100 ROM_PRNTAX = 0xF941 # Output A then X as hex
101 ROM_PRBLNK = 0xF948 # Output three spaces via hooks
102 ROM_PRBL2 = 0xF94A # Output X spaces via hooks
103 ROM_STEP = 0xFA43 # Single step (old ROM only!)
104 ROM_REGDSP = 0xFAD7 # Display working registers
105 ROM_PREAD = 0xFB1E # Read a game paddle
106 ROM_INIT = 0xFB2F # Initialize text screen
107 ROM_SETTXT = 0xFB39 # Set up text screen
108 ROM_SETGR = 0xFB40 # Setup LORES screen
109 ROM_SETWND = 0xFB4B # Set text window to normal
110 ROM_BASCALC = 0xFBC1 # Calculate text base address
111 ROM_BELL = 0xFBD9 # 1 Beep speaker if ctrl G
112 ROM_BELL2 = 0xFBE4 # Beep speaker once
113 ROM_ADVANCE = 0xFBF4 # Move text cursor right by one
114 ROM_VIDOUT = 0xFBFD # Output ASCII to screen only
115 ROM_BS = 0xFC10 # Backspace screen
116 ROM_UP = 0xFC1A # Move screen cursor up one
117 ROM_VTAB = 0xFC22 # Vertical screen tab using CV
118 ROM_VTABZ = 0xFC24 # Vertical screen tab using A
119 ROM_ESC1 = 0xFC2C # Process escape movements A-G
120 ROM_CLREOP = 0xFC42 # Clear text to end of screen
121 ROM_HOME = 0xFC58 # Clear screen and home cursor
122 ROM_CR = 0xFC62 # Carriage return to screen
123 ROM_LF = 0xFC66 # Line feed to screen onIy
124 ROM_SCROLL = 0xFC70 # Scroll text screen up one
125 ROM_CLEOL = 0xFC9C # Clear text to end of line
126 ROM_WAIT = 0xFCA8 # Time delay set by accumulator
127 ROM_RDKEY = 0xFD0C # Get input character via hooks
128 ROM_KEYIN = 0xFD1B # Read the Apple keyboard
129 ROM_RDCHAR = 0xFD35 # Get key and process ESC A-F
130 ROM_CANCEL = 0xFD62 # Cancel keyboard line entry
131 ROM_GETLNZ = 0xFD67 # CR, then get kbrl input line
132 ROM_GETLN = 0xFD6A # Get input line from keyboard
133 ROM_GETLN1 = 0xFD6F # Get kbd input, no prompt
134 ROM_CROUT1 = 0xFD8B # Clear EOL then CR via hooks
135 ROM_CROUT = 0xFD8E # Output return via hooks
136 ROM_PRBYTE = 0xFDDA # Output full A in hpxto hooks
137 ROM_PRHEX = 0xFDE3 # Output low A in hex to hooks
138 ROM_COUT = 0xFDED # Output character via hooks
139 ROM_COUT1 = 0xFDF0 # Output character to screen
140 ROM_MOVE = 0xFE2C # Move block of memory
141 ROM_VERIFY = 0xFE36 # Verify block of memory
142 ROM_LIST = 0xFE5E # Disassemble 20 instructions
143 ROM_L1ST2 = 0xFE63 # Disassemble A instructions
144 ROM_SETINV = 0xFE80 # Print inverse text on screen
145 ROM_SETNORM = 0xFE84 # Print normal text on screen
146 ROM_SETVID = 0xFE93 # Grab output hooks for screen
147 ROM_XBASIC = 0xFEB0 # Goto BASIC, destroying old
148 ROM_BASCON = 0xFEB3 # Goto BASIC continuing old
149 ROM_TRACE = 0xFEC2 # Start tracing (old ROM only!)
150 ROM_WRITE = 0xFECD # Save to cassette tape
151 ROM_READ = 0xFEFD # Read from cassette tape
152 ROM_PRERR = 0xFF2D # Print "ERR" to output hook
153 ROM_BELL = 0xFF3A # Output bell via hooks
154 ROM_IORESR = 0xFF3F # Restore all working register
155 ROM_IOSAVE = 0xFF4A # Save all working registers
156 ROM_OLDRST = 0xFF59 # Old reset entry, no autostart
157 ROM_MON = 0xFF65 # Enter monitor and beep spkr
159 # see https://imgur.com/h0NNOc3
160 # colors (map 15 apple colors to 16 VT100 colours, some duplicate or unused)
162 0x0, # black -> black
164 0x4, # d.blue -> blue
165 0x5, # purple -> magenta
166 0x2, # d.green -> green
167 0x7, # gray 1 -> white
168 0xc, # m.blue -> intense blue
169 0xd, # l.blue -> intense magenta
170 0x3, # brown -> yellow
171 0xb, # orange -> intense yellow
172 0x7, # grey 2 -> white
173 0xd, # pink -> intense magenta
174 0xa, # l.green -> intense green
175 0xb, # yellow -> intense yellow
176 0xe, # aqua -> intense cyan
177 0xf, # white -> intense white
180 # see /ribbit/ribbit.bas
181 # hrcg is loaded at DOS addr - $801 = $9600 - $801 = $8dff
183 HRCG_START = 0x8dff # initialization with banner
184 HRCG_ENTRY = HRCG_START + 3 # initialization without banner
186 # see /lemonade/lemonade_tone.lst
187 LEMONADE_TONE_PERIOD = 0x300
188 LEMONADE_TONE_DUR = 0x301
189 LEMONADE_TONE_START = 0x302
190 LEMONADE_TONE_END = 0x317
191 LEMONADE_TONE_DATA = [
204 # see /ribbit/ribbit_tone.lst
205 # it is the same as lemonade_tone except for some context save/restore
206 LEMONADE_TONE_END2 = 0x31d
207 LEMONADE_TONE_DATA2 = [
222 # see /little_brick_out/little_brick_out_tone.lst
223 LITTLE_BRICK_OUT_TONE_PERIOD = 6
224 LITTLE_BRICK_OUT_TONE_DUR = 7
225 LITTLE_BRICK_OUT_TONE_START = 0x300
226 LITTLE_BRICK_OUT_TONE_END = 0x313
227 LITTLE_BRICK_OUT_TONE_DATA = [
248 # see /lemonade/lemonade_flash_patched.lst
249 LEMONADE_FLASH_INIT = 0x3600
250 LEMONADE_FLASH_INIT_PATCHED = 0x95ef
251 LEMONADE_FLASH_EXECUTE = 0x3603
252 LEMONADE_FLASH_EXECUTE_PATCHED = 0x9586
253 lemonade_flash_color0 = 0
259 beep_style = BEEP_STYLE_ALSA
263 fd_in = sys.stdin.fileno()
264 fd_out = sys.stdout.fileno()
266 poll_in = select.poll()
267 poll_in.register(fd_in, select.POLLIN)
269 # following values were taken from a 48K Apple after normal boot and NEW
270 low_mem = [0] * 0x800
271 #low_mem[ZP_WNDLFT] = 0
272 low_mem[ZP_WNDWTH] = 40
273 #low_mem[ZP_WNDTOP] = 0
274 low_mem[ZP_WNDBTM] = 24
276 low_mem[ZP_CV] = 23 # likely value (depends on previous terminal activity)
277 #low_mem[ZP_COLOR] = 0
278 low_mem[ZP_INVFLG] = INVFLG_NORMAL
279 low_mem[ZP_LOMEM] = 4
280 low_mem[ZP_LOMEM + 1] = 8
281 #low_mem[ZP_HIMEM] = 0
282 low_mem[ZP_HIMEM + 1] = 150
290 pdl_value = [255 for i in range(4)]
295 COUT_STATE_NORMAL = 0
296 COUT_STATE_CTRL_A = 1
297 cout_state = COUT_STATE_NORMAL
299 # see https://github.com/python/cpython/blob/3.10/Lib/tty.py
309 global termios_attr, pcm
311 if termios_attr is None and os.isatty(fd_in):
312 termios_attr = termios.tcgetattr(fd_in)
313 atexit.register(deinit)
315 # deep copy the termios structure
316 attr = list(termios_attr)
317 attr[TERMIOS_CC] = list(attr[TERMIOS_CC])
319 # see cfmakeraw() in termios man page
320 attr[TERMIOS_IFLAG] &= ~(
330 attr[TERMIOS_OFLAG] &= ~termios.OPOST
331 attr[TERMIOS_LFLAG] &= ~(
338 attr[TERMIOS_CFLAG] &= ~(termios.CSIZE | termios.PARENB);
339 attr[TERMIOS_CFLAG] |= termios.CS8;
341 attr[TERMIOS_LFLAG] |= termios.ISIG
342 attr[TERMIOS_CC][termios.VINTR] = 3 # ctrl-c
343 termios.tcsetattr(fd_in, termios.TCSAFLUSH, attr)
345 # auto wrap off, hide cursor, reset attributes
346 write('\x1b[?7l\x1b[?25l\x1b[0m')
351 if termios_attr is not None:
352 # reset to initial state, auto wrap on, show cursor, reset attributes
353 # note: reset to initial state clears the screen which I do not like,
354 # but it may be the only way to clear our change to the beep settings
355 #write('\x1bc\x1b[?7h\x1b[?25h\x1b[0m')
357 # without full reset: reset scrolling region (or user must run "reset")
358 s = struct.pack('HHHH', 0, 0, 0, 0)
359 t = fcntl.ioctl(sys.stdout.fileno(), termios.TIOCGWINSZ, s)
360 h, w, _, _ = struct.unpack('HHHH', t)
361 set_scrolling_region(0, h)
363 # auto wrap on, show cursor, reset attributes
364 write('\x1b[?7h\x1b[?25h\x1b[0m')
366 termios.tcsetattr(fd_in, termios.TCSADRAIN, termios_attr)
367 atexit.unregister(deinit)
376 # don't use Python buffered I/O facility for stdin, because we don't know of
377 # a documented way to check the input buffer level, and without this we can't
378 # meaningfully use poll() to check whether an input character is available
382 # decode utf-8 character
383 _in = os.read(fd_in, 1)
384 if _in[0] & 0xe0 == 0xc0:
385 _in += os.read(fd__in, 1)
386 elif _in[0] & 0xf0 == 0xe0:
387 _in += os.read(fd_in, 2)
388 elif _in[0] & 0xf8 == 0xf0:
389 _in += os.read(fd_in, 3)
390 out.append(str(_in, 'utf-8'))
393 # don't use Python buffered I/O facility for stdout, because applesoft code
394 # uses plot/print for animation, so we'd need to flush it every time anyway
396 os.write(fd_out, bytes(data, 'utf-8'))
401 '\r\n' + (f'\x1b[{low_mem[ZP_WNDLFT]:d}C' if low_mem[ZP_WNDLFT] else '')
404 if low_mem[ZP_CV] >= low_mem[ZP_WNDBTM]:
405 low_mem[ZP_CV] = low_mem[ZP_WNDBTM] - 1
406 low_mem[ZP_CH] = low_mem[ZP_WNDLFT]
412 if cout_state == COUT_STATE_NORMAL:
414 tone(1000., .1) # 1 kHz for .1 sec
416 if low_mem[ZP_CH] > low_mem[ZP_WNDLFT]:
422 if low_mem[ZP_CV] >= low_mem[ZP_WNDBTM]:
423 low_mem[ZP_CV] = low_mem[ZP_WNDBTM] - 1
425 # apple treats \r as \r\n, so if you write e.g. \r\n you'll get \r\n\n
429 elif ord(ch) >= 0x20:
430 # some applications expect BASL, BASH = base address of current line
431 addr = bascalc(low_mem[ZP_CV])
432 low_mem[ZP_BASL] = addr & 0xff
433 low_mem[ZP_BASH] = addr >> 8
437 if invflg == INVFLG_FLASH else
439 if invflg == INVFLG_INVERSE else
442 low_mem[addr + low_mem[ZP_CH]] = data
444 if current_gr and low_mem[ZP_CV] < 20:
445 write(gr_encode([data])[0] + invflg())
449 if low_mem[ZP_CH] >= low_mem[ZP_WNDLFT] + low_mem[ZP_WNDWTH]:
453 if ord(ch) == 1: # ctrl-a
454 cout_state = COUT_STATE_CTRL_A
455 elif cout_state == COUT_STATE_CTRL_A:
456 # doesn't advance cursor
458 COUT_STATE = COUT_STATE_NORMAL
462 if current_speed < 255:
463 time.sleep(5.12 / (current_speed + 1))
470 raise Exception('end of input') # due to piping or input redirection
473 mem[HW_IOADR] = (ord(ch_in) & 0x7f) | 0x80
480 write('\x1b[?25h') # show cursor
482 write('\x1b[?25l') # hide cursor
485 mem[HW_IOADR] &= 0x7f
491 write('\x1b[?25h') # show cursor
494 # taken from get(), but doesn't do cursor
499 mem[HW_IOADR] &= 0x7f
505 elif low_mem[ZP_CH] > low_mem[ZP_WNDLFT]:
518 write('\x1b[?25l') # hide cursor
522 low_mem[ZP_CH] = (x - 1) & 0xff
526 low_mem[ZP_CV] = (y - 1) & 0xff
535 def set_scrolling_region(y0, y1):
536 # save cursor, set scrolling region, restore cursor
537 write(f'\x1b[s\x1b[{y0 + 1:d};{y1:d}r\x1b[u')
540 # move cursor home, clear to end of screen
541 # just approximates proper behaviour of clearing the window rectangle
543 f'\x1b[{low_mem[ZP_WNDTOP] + 1:d};{low_mem[ZP_WNDLFT] + 1:d}H\x1b[J'
545 low_mem[ZP_CH] = low_mem[ZP_WNDLFT]
546 low_mem[ZP_CV] = low_mem[ZP_WNDTOP]
547 for i in range(low_mem[ZP_WNDTOP], 24):
549 low_mem[addr:addr + 40] = [0xa0] * 40
552 return f'\x1b[{low_mem[ZP_CH] + 1:d}G'
555 return f'\x1b[{low_mem[ZP_CV] + 1:d}d'
560 if low_mem[ZP_INVFLG] == INVFLG_FLASH else
562 if low_mem[ZP_INVFLG] == INVFLG_INVERSE else
567 if low_mem[ZP_INVFLG] != INVFLG_NORMAL:
568 low_mem[ZP_INVFLG] = INVFLG_NORMAL
572 if low_mem[ZP_INVFLG] != INVFLG_INVERSE:
573 low_mem[ZP_INVFLG] = INVFLG_INVERSE
577 if low_mem[ZP_INVFLG] != INVFLG_FLASH:
578 low_mem[ZP_INVFLG] = INVFLG_FLASH
581 def tone(freq, dur): # Hz, s
582 if beep_style == BEEP_STYLE_VT100:
583 write(f'\x1b[10;{round(freq):d}]\x1b[11;{round(dur * 1e3):d}]\a')
584 elif beep_style == BEEP_STYLE_ALSA:
585 samples = math.floor(dur * PCM_RATE)
586 samples_last = (samples + PCM_PERIODSIZE - 1) % PCM_PERIODSIZE + 1
589 0xff if math.floor(2 * freq * i / PCM_RATE) & 1 else 0
590 for i in range(samples)
592 [0] * (PCM_PERIODSIZE - samples_last)
599 format = alsaaudio.PCM_FORMAT_U8,
600 periodsize = PCM_PERIODSIZE
605 i += pcm.write(buf[i:])
613 low_mem[ZP_HIMEM] = addr & 0xff
614 low_mem[ZP_HIMEM + 1] = addr >> 8
618 low_mem[ZP_LOMEM] = addr & 0xff
619 low_mem[ZP_LOMEM + 1] = addr >> 8
624 # sometimes the application ignores an invalid key and waits for a new one
625 #if len(ch_in) == 0 and len(poll_in.poll(POLL_TIMEOUT_MS)):
626 if len(poll_in.poll(POLL_TIMEOUT_MS)):
628 return low_mem[addr] if addr < 0x800 else mem.get(addr, 0)
630 def poke(addr, data):
634 if addr == HW_KBDSTRB:
636 mem[HW_IOADR] &= 0x7f
642 y = ((addr >> 7) & 7) | (((addr & 0x7f) // 40) << 3)
644 x = (addr & 0x7f) % 40
645 gr_update(x, y, x + 1, y + 1)
650 elif addr == ZP_WNDTOP or addr == ZP_WNDBTM:
651 set_scrolling_region(low_mem[ZP_WNDTOP], low_mem[ZP_WNDBTM])
652 elif addr == ZP_INVFLG:
657 # often-used tone routines that are installed by POKEing to addresses 768+:
659 # for lemonade or ribbit, see /test/lemonade_tone.py
660 period_count = ((low_mem[LEMONADE_TONE_PERIOD] - 1) & 0xff) + 1
661 duration_count = ((low_mem[LEMONADE_TONE_DUR] - 1) & 0xff) + 1
662 cycles = 1.37788799e-02 + duration_count * (
663 -4.21513128e-06 + 1.27999925e+02 / period_count
665 duration = 1.27361219e-05 + duration_count * (
666 2.50702246e-03 + 2.50310997e-03 / period_count
668 tone(cycles / duration, duration)
670 def little_brick_out_tone():
671 # for little brick out, see /test/little_brick_out_tone.py
672 period_count = ((low_mem[LITTLE_BRICK_OUT_TONE_PERIOD] - 1) & 0xff) + 1
673 duration_count = ((low_mem[LITTLE_BRICK_OUT_TONE_DUR] - 1) & 0xff) + 1
674 cycles = 1.37788799e-02 + duration_count * (
675 -4.21513128e-06 + 1.27999925e+02 / period_count
677 duration = 1.27091766e-05 + duration_count * (
678 2.50897802e-03 + 2.25279897e-03 / period_count
680 tone(cycles / duration, duration)
683 # - uses 16-bit duration, for longer maximum duration
684 # - uses 16-bit frequency, for improved tuning accuracy
685 # - allows zero frequency, for a "rest" or accurate delay
686 # however, duration and frequency constants in the application must be
687 # pre-computed, as code is simplified by not equalizing execution paths
689 frequency_incr = low_mem[TONE_FREQL] + (low_mem[TONE_FREQH] << 8)
690 duration_count = -(low_mem[TONE_DURL] + (low_mem[TONE_DURH] << 8))
691 duration_count = ((duration_count - 1) & 0xffff) + 1
692 duration = 3.58309497e-10 + duration_count * (
693 2.34821712e-05 + frequency_incr * 4.47591203e-11
696 period = 0x1fffe * (2.34821712e-05 / frequency_incr + 4.47591203e-11)
697 tone(1. / period, duration)
701 # the following pair of machine language subroutines for lemonade allow
702 # application to quickly replace one colour on the GR screen with another
703 def lemonade_flash_init():
704 global lemonade_flash_color0
705 lemonade_flash_color0 = low_mem[ZP_COLOR] & 0xf
707 def lemonade_flash_execute():
708 global lemonade_flash_color0
711 color1 = low_mem[ZP_COLOR] & 0xf
717 if (low_mem[addr + j] & 0xf) == lemonade_flash_color0:
718 low_mem[addr + j] = (low_mem[addr + j] & 0xf0) | color1
722 if (low_mem[addr + j] >> 4) == lemonade_flash_color0:
723 low_mem[addr + j] = (low_mem[addr + j] & 0xf) | (color1 << 4)
728 gr_update(x0, i, x1, i + 1)
729 lemonade_flash_color0 = color1
733 if addr == RBOOT_ENTRY or addr == HRCG_START or addr == HRCG_ENTRY:
734 # these do not do anything locally, as they run in the terminal instead
737 addr == LEMONADE_TONE_START and (
738 low_mem[LEMONADE_TONE_START:LEMONADE_TONE_END] == LEMONADE_TONE_DATA or
739 low_mem[LEMONADE_TONE_START:LEMONADE_TONE_END2] == LEMONADE_TONE_DATA2
744 addr == LITTLE_BRICK_OUT_TONE_START and
745 low_mem[LITTLE_BRICK_OUT_TONE_START:LITTLE_BRICK_OUT_TONE_END] ==
746 LITTLE_BRICK_OUT_TONE_DATA
748 little_brick_out_tone()
749 elif addr == TONE_REST:
750 low_mem[TONE_FREQL] = 0
751 low_mem[TONE_FREQH] = 0
753 elif addr == TONE_REST or addr == TONE_TONE:
754 # note: will be loaded with PRINT CHR$(4);"BLOAD TONE.OBJ" which we do not
755 # emulate and therefore it does not appear in low_mem[], it has to go last
756 # (after the above checks for specific tone routines) to avoid a conflict
759 addr == LEMONADE_FLASH_INIT or
760 addr == LEMONADE_FLASH_INIT_PATCHED
762 lemonade_flash_init()
764 addr == LEMONADE_FLASH_EXECUTE or
765 addr == LEMONADE_FLASH_EXECUTE_PATCHED
767 lemonade_flash_execute()
768 elif addr == ROM_CLREOP:
770 elif addr == ROM_BELL2:
771 tone(1000, 100) # 1 kHz for .1 sec
772 elif addr == ROM_HOME:
773 # note: was not in Applesoft 1 (cassette version) so can appear as CALL
775 elif addr == ROM_CLEOL:
778 raise Exception(f'call {addr:04x}')
781 return 0x400 | ((y & 7) << 7) | ((y >> 3) * 40)
786 # save cursor, set scrolling region, restore cursor
787 write('\x1b[s\x1b[1;24r\x1b[u')
788 low_mem[ZP_WNDLFT] = 0
789 low_mem[ZP_WNDWTH] = 40
790 low_mem[ZP_WNDTOP] = 0
791 low_mem[ZP_WNDBTM] = 24
797 # clear screen, set scrolling region (homes cursor)
798 #write(f'\x1b[2J\x1b[21;24r\x1b[1;21H')
799 write(f'\x1b[2J\x1b[21;24r')
800 low_mem[ZP_WNDLFT] = 0
801 low_mem[ZP_WNDWTH] = 40
802 low_mem[ZP_WNDTOP] = 20
803 low_mem[ZP_WNDBTM] = 24
808 low_mem[addr:addr + 40] = [0] * 40
811 # takes memory encoded with upper pixel in bits 0-3, lower in bits 4-7
812 # returns a stream of unicode characters with attribute-setting escapes
813 # attribute-setting can be optimized by passing in a previous attribute
814 def gr_encode(_in, attr = (-1, -1, -1, -1, -1)):
817 background = colors[i & 0xf]
818 foreground = colors[i >> 4]
819 # we cannot draw two different intense colours in same block,
820 # so just do the best we can (the greater colour gets priority)
821 if background < foreground:
823 elif background > foreground:
824 background, foreground = foreground, background
827 # we try to draw not-intense pixels using background, it
828 # might play nicer with terminals that use black-on-white
834 new_attr = (background & 7, foreground & 7, foreground >> 3, -1, -1)
837 '\x1b[0;{0:d};{1:d}{2:s}m'.format(
838 new_attr[0] + 40, # foreground
839 new_attr[1] + 30, # background
840 ';1' if new_attr[2] else '' # intense
845 return ''.join(out), attr
847 # similar to gr_encode(), but for the text portion of screen (lines 21-24)
848 # decodes hi bits of characters into attributes interleaved with characters
850 (0x40, (-1, -1, -1, 1, 0)), # inverse @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
851 (0x20, (-1, -1, -1, 1, 0)), # inverse !"#$%&'()*+,-./0123456789:;<=>?
852 (0x40, (-1, -1, -1, 1, 1)), # blink @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
853 (0x20, (-1, -1, -1, 1, 1)), # blink !"#$%&'()*+,-./0123456789:;<=>?
854 (0x40, (-1, -1, -1, 0, 0)), # normal @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
855 (0x20, (-1, -1, -1, 0, 0)), # normal !"#$%&'()*+,-./0123456789:;<=>?
856 (0x40, (-1, -1, -1, 0, 0)), # normal @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
857 (0x60, (-1, -1, -1, 0, 0)), # normal `abcdefghijklmnopqrstuvwxyz{|}~del
859 def text_encode(_in, attr = (-1, -1, -1, -1, -1)):
862 base, new_attr = text_hibits[i >> 5]
865 '\x1b[0{0:s}{1:s}m'.format(
866 ';7' if new_attr[3] else '', # reverse video
867 ';5' if new_attr[4] else '' # blink
871 out.append(chr(base | (i & 0x1f)))
872 return ''.join(out), attr
874 def gr_update(x0, y0, x1, y1):
875 write('\x1b[s') # save cursor
876 attr = (-1, -1, -1, -1, -1)
877 for i in range(y0, y1):
880 gr_encode(low_mem[addr + x0:addr + x1], attr)
881 if current_gr and i < 20 else
882 text_encode(low_mem[addr + x0:addr + x1], attr)
884 write(f'\x1b[{i + 1:d};{x0 + 1:d}H{data:s}')
885 write('\x1b[u' + invflg()) # restore cursor and normal/inverse/flash
888 low_mem[ZP_COLOR] = n
891 color = low_mem[ZP_COLOR] & 0xf
896 low_mem[addr + x] = (low_mem[addr + x] & 0xf0) | color
898 low_mem[addr + x] = (low_mem[addr + x] & 0xf) | (color << 4)
899 gr_update(x, j, x + 1, j + 1)
902 color = low_mem[ZP_COLOR] & 0xf
909 for x in range(x0, x1 + 1):
910 low_mem[addr + x] = (low_mem[addr + x] & 0xf0) | color
912 for x in range(x0, x1 + 1):
913 low_mem[addr + x] = (low_mem[addr + x] & 0xf) | (color << 4)
914 gr_update(x0, j, x1 + 1, j + 1)
917 color = low_mem[ZP_COLOR] & 0xf
920 for y in range(y0, y1 + 1):
925 low_mem[addr + x] = (low_mem[addr + x] & 0xf0) | color
927 low_mem[addr + x] = (low_mem[addr + x] & 0xf) | (color << 4)
928 gr_update(x, y0 >> 1, x + 1, (y1 >> 1) + 1)
938 return low_mem[addr + x] & 0xf if i == 0 else low_mem[addr + x] >> 4
944 return low_mem[ZP_CH]
948 current_speed = n & 0xff