#!/usr/bin/env python3
+import alsaaudio
import atexit
import os
import select
import time
import tty
+PCM_RATE = 44100
+PCM_PERIODSIZE = 44100
+
# see https://www.tinaja.com/ebooks/tearing_rework.pdf
ZP_WNDLFT = 0x20 # Left side of scroll window
ZP_WNDWTH = 0x21 # Width of scroll window
mem = {
ZP_CH: 0,
ZP_CV: 0,
- 0x300: 0, # tone freq
+ 0x300: 0, # tone period
0x301: 0, # tone dur
}
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
+)
def init():
global attr
def _print(data):
for ch in data:
- if ch == '\r':
+ if ch == '\a':
+ tone(1000, 100) # 1 kHz for .1 sec
+ 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_CH] = 0
else:
write(ch)
- if ch == '\a':
- pass
- elif ch == '\b':
+ if ch == '\b':
if mem[ZP_CH] > 0:
mem[ZP_CH] -= 1
elif mem[ZP_CH] < 39:
ch = read(1)
if len(ch) == 0:
raise Exception('end of input') # due to piping or input redirection
+ if ch == '\x03':
+ raise Exception('user break')
if ch == '\x7f':
ch = '\b'
return ch
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')
- time.sleep(dur * .001)
+ 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)
+ t = time.monotonic()
def himem(addr):
pass
def call(addr):
addr &= 0xffff
if addr == 0x302:
- tone(mem[0x300], mem[0x301])
+ # for lemonade, see test/timing.py
+ tone(
+ round(49273.64 / mem[0x300]), # freq
+ round(mem[0x301] * 2.579329) # dur
+ )
elif addr == 0x3600:
- # lighting (setup)
+ # for lemonade, lighting (setup)
pass
elif addr == 0x3603:
- # lightning (execute)
+ # for lemonade, lightning (execute)
pass
elif addr == ROM_CLREOP:
clreop()
+ elif addr == ROM_BELL2:
+ tone(1000, 100) # 1 kHz for .1 sec
else:
raise Exception(f'call {addr:04x}')
br = new_br
write(ch * 2)
write('\x1b[u\x1b[0m') # restore cursor and attrs
- time.sleep(.1)
+ time.sleep(.05)
def color(n):
global gr_color
--- /dev/null
+#!/usr/bin/env python3
+
+import py65.devices.mpu6502
+import sys
+
+CPU_HZ = 3579545 * 4 / 14 # 1.02272714 MHz (from NTSC clock)
+
+mem = [0] * 0x10000
+
+# lemonade.bas
+
+# 10100POKE770,173:POKE771,48:POKE772,192:POKE773,136:POKE774,208:POKE775,5:POKE776,206:POKE777,1:POKE778,3:POKE779,240:POKE780,9:POKE781,202
+mem[770] = 173
+mem[771] = 48
+mem[772] = 192
+mem[773] = 136
+mem[774] = 208
+mem[775] = 5
+mem[776] = 206
+mem[777] = 1
+mem[778] = 3
+mem[779] = 240
+mem[780] = 9
+mem[781] = 202
+
+#10110POKE782,208:POKE783,245:POKE784,174:POKE785,0:POKE786,3:POKE787,76:POKE788,2:POKE789,3:POKE790,96:POKE791,0:POKE792,0
+mem[782] = 208
+mem[783] = 245
+mem[784] = 174
+mem[785] = 0
+mem[786] = 3
+mem[787] = 76
+mem[788] = 2
+mem[789] = 3
+mem[790] = 96
+#mem[791] = 0
+#mem[792] = 0
+
+cpu = py65.devices.mpu6502.MPU(memory = mem, pc = 0x400)
+
+print('jsr 0x0302')
+mem[0x400] = 0x20
+mem[0x401] = 0x02
+mem[0x402] = 0x03
+total_duration_count = 0
+total_duration_secs = 0.
+total_period_count = 0
+total_period_secs = 0.
+for duration_count in [0x10, 0x20, 0x40, 0x80]:
+ for period_count in [0x10, 0x20, 0x40, 0x80]:
+ mem[0x300] = period_count
+ mem[0x301] = duration_count
+ cpu.reset()
+ toggles = 0
+ while cpu.pc != 0x403:
+ if cpu.pc == 0x302:
+ toggles += 1
+ cpu.step()
+ duration_secs = cpu.processorCycles / CPU_HZ
+ period_secs = duration_secs * 2 / toggles
+ frequency = 1. / period_secs
+ print(f'duration_count {duration_count:02x} duration_secs {duration_secs:.6f} period_count {period_count:02x} period_secs {period_secs:.9f} frequency {frequency:10.3f}')
+ total_duration_count += duration_count
+ total_duration_secs += duration_secs
+ total_period_count += period_count
+ total_period_secs += period_secs
+print(f'duration_secs / duration_count = {total_duration_secs / total_duration_count:.6e}')
+print(f'period_secs / period_count = {total_period_secs / total_period_count:.6e}')
+print(f'period_count / period_secs = {total_period_count / total_period_secs:.6e}')
+print()
+
+# monitor ROM
+
+# fca8: 38 WAIT sec
+# fca9: 48 WAIT2 pha
+# fcaa: e9 01 WAIT3 sbc #$01 ;1.0204 usec [wrong]
+# fcac: d0 fc bne WAIT3 ;(13+2712*A+512*A*A) [wrong]
+# fcae: 68 pla
+# fcaf: e9 01 sbc #$01
+# fcb1: d0 f6 bne WAIT2
+# fcb3: 60 rts
+mem[0xfca8] = 0x38
+mem[0xfca9] = 0x48
+mem[0xfcaa] = 0xe9
+mem[0xfcab] = 0x01
+mem[0xfcac] = 0xd0
+mem[0xfcad] = 0xfc
+mem[0xfcae] = 0x68
+mem[0xfcaf] = 0xe9
+mem[0xfcb0] = 0x01
+mem[0xfcb1] = 0xd0
+mem[0xfcb2] = 0xf6
+mem[0xfcb3] = 0x60
+
+print('jsr 0xfca8')
+mem[0x400] = 0x20 # jsr
+mem[0x401] = 0xa8
+mem[0x402] = 0xfc
+total_duration_count = 0
+total_duration_secs = 0.
+for duration_count in [0x10, 0x20, 0x40, 0x80]:
+ cpu.reset()
+ cpu.a = duration_count
+ while cpu.pc != 0x403:
+ cpu.step()
+ duration_secs = cpu.processorCycles / CPU_HZ
+ frequency = 1. / period_secs
+ print(f'duration_count {duration_count:02x} duration_secs {duration_secs:.6f}')
+ total_duration_count += duration_count
+ total_duration_secs += duration_secs
+print(f'duration_secs / duration_count = {total_duration_secs / total_duration_count:.6e}')
+print()