-#!/usr/bin/env python3
-
import alsaaudio
import atexit
+import math
import os
import select
import sys
ROM_STEP = 0xFA43 # Single step (old ROM only!)
ROM_REGDSP = 0xFAD7 # Display working registers
ROM_PREAD = 0xFB1E # Read a game paddle
-ROM_INIT = 0xFB2F # Initial i ze text screen
+ROM_INIT = 0xFB2F # Initialize text screen
ROM_SETTXT = 0xFB39 # Set up text screen
ROM_SETGR = 0xFB40 # Setup LORES screen
ROM_SETWND = 0xFB4B # Set text window to normal
ROM_UP = 0xFC1A # Move screen cursor up one
ROM_VTAB = 0xFC22 # Vertical screen tab using CV
ROM_VTABZ = 0xFC24 # Vertical screen tab using A
-ROM_ESCl = 0xFC2C # Process escape movements A-G
+ROM_ESC1 = 0xFC2C # Process escape movements A-G
ROM_CLREOP = 0xFC42 # Clear text to end of screen
ROM_HOME = 0xFC58 # Clear screen and home cursor
ROM_CR = 0xFC62 # Carriage return to screen
NICK_FREQL = 0x30d
NICK_FREQH = 0x314
+# command line
+BEEP_STYLE_ALSA = 0
+BEEP_STYLE_VT100 = 1
+
+beep_style = BEEP_STYLE_ALSA
+gr_width = 1
+
# global state
-gr_width = 1 # set by command line
termios_attr = None
fd_in = sys.stdin.fileno()
fd_out = sys.stdout.fileno()
}
gr_color = 0
gr_mem = [[0 for j in range(40)] for i in range(40)]
-pcm = alsaaudio.PCM(
- device = 'default',
- channels = 1,
- rate = PCM_RATE,
- format = alsaaudio.PCM_FORMAT_U8,
- periodsize = PCM_PERIODSIZE
-)
+pcm = None
def init():
- global termios_attr
+ global termios_attr, pcm
if termios_attr is None and os.isatty(fd_in):
termios_attr = termios.tcgetattr(fd_in)
atexit.register(deinit)
tty.setraw(fd_in)
- write('\x1b[?25l\x1b[0m') # hide cursor, reset attributes
+ # auto wrap off, hide cursor, reset attributes
+ write('\x1b[?7l\x1b[?25l\x1b[0m')
+
+ if beep_style == BEEP_STYLE_ALSA:
+ pcm = alsaaudio.PCM(
+ device = 'default',
+ channels = 1,
+ rate = PCM_RATE,
+ format = alsaaudio.PCM_FORMAT_U8,
+ periodsize = PCM_PERIODSIZE
+ )
def deinit():
global termios_attr
if termios_attr is not None:
- write('\x1b[?25h\x1b[0m') # show cursor, reset attributes
+ # reset to initial state, auto wrap on, show cursor, reset attributes
+ # note: reset to initial state clears the screen which I do not like,
+ # but it may be the only way to clear our change to the beep settings
+ #write('\x1bc\x1b[?7h\x1b[?25h\x1b[0m')
+ write('\x1b[?7h\x1b[?25h\x1b[0m')
+
termios.tcsetattr(fd_in, termios.TCSADRAIN, termios_attr)
atexit.unregister(deinit)
termios_attr = None
def _print(data):
for ch in data:
if ch == '\a':
- tone(1000, 100) # 1 kHz for .1 sec
+ tone(1000., .1) # 1 kHz for .1 sec
+ elif ch == '\b':
+ if mem[ZP_CH] > mem[ZP_WNDLFT]:
+ write('\b')
+ mem[ZP_CH] -= 1
+ elif ch == '\n':
+ write('\n')
+ mem[ZP_CV] += 1
+ if mem[ZP_CV] >= mem[ZP_WNDBTM]:
+ mem[ZP_CV] = mem[ZP_WNDBTM] - 1
elif ch == '\r':
# apple treats \r as \r\n, so if you write e.g. \r\n you'll get \r\n\n
- write('\r\n')
- if mem[ZP_CV] < 23:
- mem[ZP_CV] += 1
- mem[ZP_CH] = 0
- else:
+ write(
+ '\r\n' + (f'\x1b[{mem[ZP_WNDLFT]:d}C' if mem[ZP_WNDLFT] else '')
+ )
+ mem[ZP_CV] += 1
+ if mem[ZP_CV] >= mem[ZP_WNDBTM]:
+ mem[ZP_CV] = mem[ZP_WNDBTM] - 1
+ mem[ZP_CH] = mem[ZP_WNDLFT]
+ elif ord(ch) >= 0x20:
write(ch)
- if ch == '\b':
- if mem[ZP_CH] > 0:
- mem[ZP_CH] -= 1
- elif mem[ZP_CH] < 39:
- mem[ZP_CH] += 1
- else:
- write('\r\n')
- if mem[ZP_CV] < 23:
- mem[ZP_CV] += 1
- mem[ZP_CH] = 0
+ mem[ZP_CH] += 1
+ if mem[ZP_CH] >= mem[ZP_WNDLFT] + mem[ZP_WNDWTH]:
+ write(
+ '\r\n' + (f'\x1b[{mem[ZP_WNDLFT]:d}C' if mem[ZP_WNDLFT] else '')
+ )
+ mem[ZP_CV] += 1
+ if mem[ZP_CV] >= mem[ZP_WNDBTM]:
+ mem[ZP_CV] = mem[ZP_WNDBTM] - 1
+ mem[ZP_CH] = mem[ZP_WNDLFT]
def get():
ch = read(1)
ch = get()
if ch == '\b':
if len(line):
- _print('\b \b')
+ _print('\b')
line = line[:-1]
- elif mem[ZP_CH]:
+ elif mem[ZP_CH] > mem[ZP_WNDLFT]:
_print('\r')
else:
_print(ch)
write('\x1b[J')
def home():
- write('\x1b[2J\x1b[H')
- mem[ZP_CH] = 0
- mem[ZP_CV] = 0
+ write(f'\x1b[2J\x1b[{mem[ZP_WNDLFT] + 1:d};{mem[ZP_WNDTOP] + 1:d}H')
+ mem[ZP_CH] = mem[ZP_WNDLFT]
+ mem[ZP_CV] = mem[ZP_WNDTOP]
def normal():
write('\x1b[0m')
def flash():
write('\x1b[0;7;5m')
-def tone(freq, dur): # Hz, ms
- # doesn't seem to work on any linux console on my laptop
- #write(f'\x1b7\x1b[10;{freq:d}]\x1b[11;{dur:d}]\x07\x1b8')
- u = time.monotonic() + dur * .001
- buf = bytes(
- [
- 0xff if (2 * freq * i // PCM_RATE) & 1 else 0
- for i in range(PCM_RATE * dur // 1000)
- ]
- )
- i = 0
- while i < len(buf):
- i += pcm.write(buf[i:])
- t = time.monotonic()
- while t < u:
- time.sleep(u - t)
+def tone(freq, dur): # Hz, s
+ if beep_style == BEEP_STYLE_VT100:
+ write(f'\x1b[10;{round(freq):d}]\x1b[11;{round(dur * 1e3):d}]\a')
+ elif beep_style == BEEP_STYLE_ALSA:
+ buf = bytes(
+ [
+ 0xff if math.floor(2 * freq * i / PCM_RATE) & 1 else 0
+ for i in range(math.floor(dur * PCM_RATE))
+ ]
+ )
+ u = time.monotonic() + dur
+ i = 0
+ while i < len(buf):
+ i += pcm.write(buf[i:])
t = time.monotonic()
+ while t < u:
+ time.sleep(u - t)
+ t = time.monotonic()
+ else:
+ assert False
def himem(addr):
pass
mem[addr] = data
if addr == ZP_WNDTOP or addr == ZP_WNDBTM:
# save cursor, set scrolling region, restore cursor
- write(f'\x1b[s\x1b{mem[ZP_WNDTOP + 1]:d};{mem[ZP_WNDBTM]:d}24r\x1b[u')
+ write(f'\x1b[s\x1b[{mem[ZP_WNDTOP + 1]:d};{mem[ZP_WNDBTM]:d}r\x1b[u')
def call(addr):
addr &= 0xffff
)
if frequency_incr:
period = 0x1fffe * (2.34821712e-05 / frequency_incr + 4.47591203e-11)
- tone(round(1. / period), round(duration * 1e3))
+ tone(1. / period, duration)
else:
time.sleep(duration)
elif addr == TONE_TONE or addr == TONE_TONE1:
duration = 1.27361219e-05 + duration_count * (
2.50702246e-03 + 2.50310997e-03 / period_count
)
- tone(round(cycles / duration), round(duration * 1e3))
+ tone(cycles / duration, duration)
elif addr == 0x3600 or addr == 0x95ef:
# for lemonade, lighting (init)
pass
gr_mem[i][:] = [0 for j in range(40)]
def gr_update(x0, y0, x1, y1):
- write('\x1b[s') # save cursor
+ #write('\x1b[s') # save cursor
+ write(f'\x1b7') # save all attributes (in case in INVERSE/FLASH mode)
bg = -1
fg = -1
br = -1
fg = new_fg
br = new_br
write(ch * gr_width)
- write('\x1b[u\x1b[0m') # restore cursor and attributes
- #time.sleep(.05)
+ #write('\x1b[u\x1b[0m') # restore cursor and attributes
+ write(f'\x1b8') # save all attributes (in case in INVERSE/FLASH mode)
def color(n):
global gr_color
def pos():
return mem[ZP_CH]
-
-if __name__ == '__main__':
- init()
- while True:
- while not read_ready():
- pass
- k = ord(read(1))
- write(f'k {k:02x}\r')
- if k == 0x1b:
- break
- #for i in range(12):
- # tone(int(round(220 * 2 ** (i / 12.))), 250)
- deinit()