Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- local component = require("component")
- local computer = require("computer")
- local shell = require("shell")
- local fs = require("filesystem")
- local PUMP_INTERVAL = 0.15
- local RENDER_EVERY = 2
- local MASTER_VOLUME = 0.6
- local FRAME_TS = 69888
- local CPU_HZ = 3500000
- local CONTENTION = true
- local KEY_HOLD = 3
- local AUTOLOAD_FRAME = 150
- local args = {...}
- local gpu = component.gpu
- local snd = component.isAvailable("sound") and component.sound or nil
- local function readFile(path)
- local p = shell.resolve(path)
- local f = io.open(p, "rb")
- if not f then return nil end
- local d = f:read("*a")
- f:close()
- return d
- end
- local function rd16(d, i)
- return d:byte(i) | (d:byte(i + 1) << 8)
- end
- local function rd24(d, i)
- return d:byte(i) | (d:byte(i + 1) << 8) | (d:byte(i + 2) << 16)
- end
- local function rd32(d, i)
- return d:byte(i) | (d:byte(i + 1) << 8) |
- (d:byte(i + 2) << 16) | (d:byte(i + 3) << 24)
- end
- local romPath, snaPath, tapPath, z80Path, tzxPath, scrPath, binPath
- for _, a in ipairs(args) do
- local ext = a:lower():match("%.(%w+)$")
- if ext == "rom" then
- romPath = a
- elseif ext == "bin" then
- binPath = a
- elseif ext == "sna" then
- snaPath = a
- elseif ext == "tap" then
- tapPath = a
- elseif ext == "z80" then
- z80Path = a
- elseif ext == "tzx" then
- tzxPath = a
- elseif ext == "scr" then
- scrPath = a
- else
- io.stderr:write("zx: unknown file type: " .. a .. "\n")
- return
- end
- end
- local romData
- do
- local candidates = {
- romPath,
- "48.rom",
- "/home/48.rom",
- "/usr/share/zx/48.rom"
- }
- for _, p in ipairs(candidates) do
- if p and fs.exists(shell.resolve(p)) then
- romData = readFile(p)
- if romData then break end
- end
- end
- end
- if not romData or #romData ~= 16384 then
- io.stderr:write("zx: need the 16384-byte 48K ROM image (48.rom).\n")
- io.stderr:write("usage: zx [48.rom] [game.sna|.z80|.tap|.tzx|.scr|.bin]\n")
- return
- end
- local snaData, tapBlocks, z80Data, tzxData, scrData, binData
- if snaPath then
- snaData = readFile(snaPath)
- if not snaData then
- io.stderr:write("zx: cannot read " .. snaPath .. "\n")
- return
- end
- if #snaData ~= 49179 then
- io.stderr:write("zx: only 48K .sna snapshots (49179 bytes) are supported\n")
- return
- end
- end
- if z80Path then
- z80Data = readFile(z80Path)
- if not z80Data then
- io.stderr:write("zx: cannot read " .. z80Path .. "\n")
- return
- end
- if #z80Data < 30 then
- io.stderr:write("zx: .z80 file too short\n")
- return
- end
- end
- if tzxPath then
- tzxData = readFile(tzxPath)
- if not tzxData then
- io.stderr:write("zx: cannot read " .. tzxPath .. "\n")
- return
- end
- end
- if scrPath then
- scrData = readFile(scrPath)
- if not scrData or #scrData ~= 6912 then
- io.stderr:write("zx: .scr must be exactly 6912 bytes\n")
- return
- end
- end
- if binPath then
- binData = readFile(binPath)
- if not binData then
- io.stderr:write("zx: cannot read " .. binPath .. "\n")
- return
- end
- if #binData > 49152 then
- io.stderr:write("zx: .bin larger than 48K\n")
- return
- end
- end
- if tapPath then
- local d = readFile(tapPath)
- if not d then
- io.stderr:write("zx: cannot read " .. tapPath .. "\n")
- return
- end
- tapBlocks = {}
- local p = 1
- while p + 1 <= #d do
- local len = d:byte(p) | (d:byte(p + 1) << 8)
- if p + 1 + len > #d then break end
- tapBlocks[#tapBlocks + 1] = d:sub(p + 2, p + 1 + len)
- p = p + 2 + len
- end
- end
- local function loadTZX(d)
- local blocks = {}
- local p = 1
- local n = #d
- if d:sub(1, 7) == "ZXTape!" then
- p = 11
- end
- while p <= n do
- local id = d:byte(p)
- p = p + 1
- if id == 0x10 then
- local len = rd16(d, p + 2)
- blocks[#blocks + 1] = d:sub(p + 4, p + 3 + len)
- p = p + 4 + len
- elseif id == 0x11 then
- local len = rd24(d, p + 15)
- blocks[#blocks + 1] = d:sub(p + 18, p + 17 + len)
- p = p + 18 + len
- elseif id == 0x12 then
- p = p + 4
- elseif id == 0x13 then
- p = p + 1 + d:byte(p) * 2
- elseif id == 0x14 then
- p = p + 10 + rd24(d, p + 7)
- elseif id == 0x15 then
- p = p + 8 + rd24(d, p + 5)
- elseif id == 0x18 or id == 0x19 then
- p = p + 4 + rd32(d, p)
- elseif id == 0x20 or id == 0x23 or id == 0x24 then
- p = p + 2
- elseif id == 0x21 or id == 0x30 then
- p = p + 1 + d:byte(p)
- elseif id == 0x22 or id == 0x25 or id == 0x27 then
- elseif id == 0x26 then
- p = p + 2 + rd16(d, p) * 2
- elseif id == 0x28 or id == 0x32 then
- p = p + 2 + rd16(d, p)
- elseif id == 0x2A then
- p = p + 4
- elseif id == 0x2B then
- p = p + 5
- elseif id == 0x31 then
- p = p + 2 + d:byte(p + 1)
- elseif id == 0x33 then
- p = p + 1 + d:byte(p) * 3
- elseif id == 0x34 then
- p = p + 8
- elseif id == 0x35 then
- p = p + 20 + rd32(d, p + 16)
- elseif id == 0x40 then
- p = p + 4 + rd24(d, p + 1)
- elseif id == 0x5A then
- p = p + 9
- else
- break
- end
- end
- return blocks
- end
- if tzxData then
- tapBlocks = loadTZX(tzxData)
- end
- local tapePos = 1
- local M = {}
- for i = 0, 65535 do M[i] = 0 end
- for i = 0, 16383 do M[i] = romData:byte(i + 1) end
- local dirty = {}
- local anyDirty = true
- local ox, oy = 0, 0
- local border = 7
- local borderDirty = true
- local flashOn = false
- local ts = 0
- local frameStart = 0
- local function markScreenByte(a)
- if a < 0x4000 or a > 0x5AFF then return end
- if a < 0x5800 then
- local y = ((a >> 5) & 0xC0) | ((a >> 2) & 0x38) | ((a >> 8) & 7)
- dirty[(y >> 2) * 32 + (a & 31)] = true
- else
- local n = a - 0x5800
- local k = (n >> 5) * 64 + (n & 31)
- dirty[k] = true
- dirty[k + 32] = true
- end
- anyDirty = true
- end
- local CT = {[0]=6, 5, 4, 3, 2, 1, 0, 0}
- local function wr(a, v)
- a = a & 0xFFFF
- v = v & 255
- if a >= 0x4000 then
- if CONTENTION and a < 0x8000 then
- local t = ts - frameStart - 14335
- if t >= 0 and t < 43008 then
- local c = t % 224
- if c < 128 then
- ts = ts + CT[c & 7]
- end
- end
- end
- if M[a] ~= v then
- M[a] = v
- markScreenByte(a)
- end
- end
- end
- local R = {[0]=0,0,0,0,0,0,0,0}
- local S = {[0]=0,0,0,0,0,0,0,0}
- local F, F2 = 0, 0
- local PC, SP, IX, IY = 0, 0, 0, 0
- local I = 0
- local RR = 0
- local IFF1, IFF2 = false, false
- local IM = 0
- local halted = false
- local eiDelay = 0
- local prefixed = false
- local sh, sl = 0, 0
- local function incR()
- RR = (RR & 0x80) | ((RR + 1) & 0x7F)
- end
- local function fetch()
- local v = M[PC]
- PC = (PC + 1) & 0xFFFF
- return v
- end
- local function fetchOp()
- local v = M[PC]
- PC = (PC + 1) & 0xFFFF
- RR = (RR & 0x80) | ((RR + 1) & 0x7F)
- return v
- end
- local function fetch16()
- local lo = fetch()
- local hi = fetch()
- return lo | (hi << 8)
- end
- local function push(v)
- SP = (SP - 2) & 0xFFFF
- wr(SP, v & 255)
- wr((SP + 1) & 0xFFFF, (v >> 8) & 255)
- end
- local function pop()
- local lo = M[SP]
- local hi = M[(SP + 1) & 0xFFFF]
- SP = (SP + 2) & 0xFFFF
- return lo | (hi << 8)
- end
- local function getrp(p)
- if p == 0 then
- return (R[0] << 8) | R[1]
- elseif p == 1 then
- return (R[2] << 8) | R[3]
- elseif p == 2 then
- return (R[4] << 8) | R[5]
- else
- return SP
- end
- end
- local function setrp(p, v)
- v = v & 0xFFFF
- if p == 0 then
- R[0], R[1] = v >> 8, v & 255
- elseif p == 1 then
- R[2], R[3] = v >> 8, v & 255
- elseif p == 2 then
- R[4], R[5] = v >> 8, v & 255
- else
- SP = v
- end
- end
- local SZ53, SZ53P = {}, {}
- for i = 0, 255 do
- local v = i
- v = v ~ (v >> 4)
- v = v ~ (v >> 2)
- v = v ~ (v >> 1)
- local b = (i & 0xA8) | ((i == 0) and 0x40 or 0)
- SZ53[i] = b
- SZ53P[i] = b | (((v & 1) == 0) and 4 or 0)
- end
- local CM = {[0]=0x40,0x01,0x04,0x80}
- local function cc(y)
- local set = (F & CM[y >> 1]) ~= 0
- if (y & 1) == 0 then
- return not set
- end
- return set
- end
- local function alu(op, v)
- local a = R[7]
- if op == 0 or op == 1 then
- local r = a + v + ((op == 1) and (F & 1) or 0)
- local res = r & 255
- F = SZ53[res]
- | ((r >> 8) & 1)
- | ((a ~ v ~ res) & 0x10)
- | (((~(a ~ v)) & (a ~ res) & 0x80) >> 5)
- R[7] = res
- elseif op == 2 or op == 3 or op == 7 then
- local r = a - v - ((op == 3) and (F & 1) or 0)
- local res = r & 255
- local fl = ((r >> 8) & 1)
- | 2
- | ((a ~ v ~ res) & 0x10)
- | (((a ~ v) & (a ~ res) & 0x80) >> 5)
- if op == 7 then
- F = (SZ53[res] & 0xC0)
- | (v & 0x28)
- | (fl & 0x17)
- else
- F = SZ53[res] | fl
- R[7] = res
- end
- elseif op == 4 then
- local res = a & v
- F = SZ53P[res] | 0x10
- R[7] = res
- elseif op == 5 then
- local res = a ~ v
- F = SZ53P[res]
- R[7] = res
- else
- local res = a | v
- F = SZ53P[res]
- R[7] = res
- end
- end
- local function inc8(v)
- local res = (v + 1) & 255
- F = (F & 1)
- | SZ53[res]
- | (((v & 15) == 15) and 0x10 or 0)
- | ((v == 0x7F) and 4 or 0)
- return res
- end
- local function dec8(v)
- local res = (v - 1) & 255
- F = (F & 1)
- | SZ53[res]
- | 2
- | (((v & 15) == 0) and 0x10 or 0)
- | ((v == 0x80) and 4 or 0)
- return res
- end
- local function rot(op, v)
- local c, r
- if op == 0 then
- c = v >> 7
- r = ((v << 1) | c) & 255
- elseif op == 1 then
- c = v & 1
- r = (v >> 1) | (c << 7)
- elseif op == 2 then
- c = v >> 7
- r = ((v << 1) | (F & 1)) & 255
- elseif op == 3 then
- c = v & 1
- r = (v >> 1) | ((F & 1) << 7)
- elseif op == 4 then
- c = v >> 7
- r = (v << 1) & 255
- elseif op == 5 then
- c = v & 1
- r = (v >> 1) | (v & 0x80)
- elseif op == 6 then
- c = v >> 7
- r = ((v << 1) | 1) & 255
- else
- c = v & 1
- r = v >> 1
- end
- F = SZ53P[r] | c
- return r
- end
- local function ea()
- if prefixed then
- local d = fetch()
- if d >= 128 then d = d - 256 end
- local base = (R[4] << 8) | R[5]
- return (base + d) & 0xFFFF
- end
- return (R[4] << 8) | R[5]
- end
- local spk = 0
- local tgN = 0
- local tgT = {}
- local tgL = {}
- local kb = {}
- local kcount = {}
- for r = 0, 7 do
- kb[r] = 0x1F
- end
- local function portIn(p)
- if (p & 1) == 0 then
- local k = 0x1F
- local hi = (p >> 8) & 255
- for r = 0, 7 do
- if (hi & (1 << r)) == 0 then
- k = k & kb[r]
- end
- end
- return 0xA0 | k
- end
- if (p & 0xFF) == 0x1F then
- return 0
- end
- return 0xFF
- end
- local function portOut(p, v)
- if (p & 1) == 0 then
- local b = v & 7
- if b ~= border then
- border = b
- borderDirty = true
- end
- local s = (v >> 4) & 1
- if s ~= spk then
- spk = s
- if snd and tgN < 4096 then
- tgN = tgN + 1
- tgT[tgN] = ts
- tgL[tgN] = s
- end
- end
- end
- end
- local function exec(op)
- local x = op >> 6
- if x == 1 then
- local y = (op >> 3) & 7
- local z = op & 7
- if z == 6 then
- if y == 6 then
- halted = true
- return 4
- end
- local a = ea()
- local v = M[a]
- if prefixed and (y == 4 or y == 5) then
- if y == 4 then sh = v else sl = v end
- else
- R[y] = v
- end
- return prefixed and 19 or 7
- elseif y == 6 then
- local a = ea()
- local v
- if prefixed and (z == 4 or z == 5) then
- v = (z == 4) and sh or sl
- else
- v = R[z]
- end
- wr(a, v)
- return prefixed and 19 or 7
- else
- R[y] = R[z]
- return 4
- end
- elseif x == 2 then
- local y = (op >> 3) & 7
- local z = op & 7
- if z == 6 then
- alu(y, M[ea()])
- return prefixed and 19 or 7
- end
- alu(y, R[z])
- return 4
- elseif x == 0 then
- local y = (op >> 3) & 7
- local z = op & 7
- if z == 0 then
- if y == 0 then
- return 4
- elseif y == 1 then
- R[7], S[7] = S[7], R[7]
- F, F2 = F2, F
- return 4
- elseif y == 2 then
- local d = fetch()
- local b = (R[0] - 1) & 255
- R[0] = b
- if b ~= 0 then
- PC = (PC + (d >= 128 and d - 256 or d)) & 0xFFFF
- return 13
- end
- return 8
- elseif y == 3 then
- local d = fetch()
- PC = (PC + (d >= 128 and d - 256 or d)) & 0xFFFF
- return 12
- else
- local d = fetch()
- if cc(y - 4) then
- PC = (PC + (d >= 128 and d - 256 or d)) & 0xFFFF
- return 12
- end
- return 7
- end
- elseif z == 1 then
- local p = y >> 1
- if (y & 1) == 0 then
- setrp(p, fetch16())
- return prefixed and 14 or 10
- end
- local hl = (R[4] << 8) | R[5]
- local rr = getrp(p)
- local r = hl + rr
- F = (F & 0xC4)
- | ((r >> 8) & 0x28)
- | (((hl ~ rr ~ r) >> 8) & 0x10)
- | ((r >> 16) & 1)
- r = r & 0xFFFF
- R[4], R[5] = r >> 8, r & 255
- return prefixed and 15 or 11
- elseif z == 2 then
- local p = y >> 1
- if (y & 1) == 0 then
- if p == 0 then
- wr((R[0] << 8) | R[1], R[7])
- return 7
- elseif p == 1 then
- wr((R[2] << 8) | R[3], R[7])
- return 7
- elseif p == 2 then
- local a = fetch16()
- wr(a, R[5])
- wr((a + 1) & 0xFFFF, R[4])
- return 16
- else
- wr(fetch16(), R[7])
- return 13
- end
- else
- if p == 0 then
- R[7] = M[(R[0] << 8) | R[1]]
- return 7
- elseif p == 1 then
- R[7] = M[(R[2] << 8) | R[3]]
- return 7
- elseif p == 2 then
- local a = fetch16()
- R[5] = M[a]
- R[4] = M[(a + 1) & 0xFFFF]
- return 16
- else
- R[7] = M[fetch16()]
- return 13
- end
- end
- elseif z == 3 then
- local p = y >> 1
- local v = getrp(p)
- if (y & 1) == 0 then
- setrp(p, (v + 1) & 0xFFFF)
- else
- setrp(p, (v - 1) & 0xFFFF)
- end
- return prefixed and 10 or 6
- elseif z == 4 then
- if y == 6 then
- local a = ea()
- wr(a, inc8(M[a]))
- return prefixed and 23 or 11
- end
- R[y] = inc8(R[y])
- return 4
- elseif z == 5 then
- if y == 6 then
- local a = ea()
- wr(a, dec8(M[a]))
- return prefixed and 23 or 11
- end
- R[y] = dec8(R[y])
- return 4
- elseif z == 6 then
- if y == 6 then
- local a = ea()
- wr(a, fetch())
- return prefixed and 19 or 10
- end
- R[y] = fetch()
- return 7
- else
- local a = R[7]
- if y == 0 then
- local c = a >> 7
- a = ((a << 1) | c) & 255
- R[7] = a
- F = (F & 0xC4) | (a & 0x28) | c
- elseif y == 1 then
- local c = a & 1
- a = (a >> 1) | (c << 7)
- R[7] = a
- F = (F & 0xC4) | (a & 0x28) | c
- elseif y == 2 then
- local c = a >> 7
- a = ((a << 1) | (F & 1)) & 255
- R[7] = a
- F = (F & 0xC4) | (a & 0x28) | c
- elseif y == 3 then
- local c = a & 1
- a = (a >> 1) | ((F & 1) << 7)
- R[7] = a
- F = (F & 0xC4) | (a & 0x28) | c
- elseif y == 4 then
- local f = F
- local corr = 0
- local c = f & 1
- if (f & 0x10) ~= 0 or (a & 15) > 9 then
- corr = 6
- end
- if c ~= 0 or a > 0x99 then
- corr = corr | 0x60
- c = 1
- else
- c = 0
- end
- local na
- local h
- if (f & 2) ~= 0 then
- na = (a - corr) & 255
- h = (((f & 0x10) ~= 0) and (a & 15) < 6) and 0x10 or 0
- else
- na = (a + corr) & 255
- h = ((a & 15) > 9) and 0x10 or 0
- end
- R[7] = na
- F = SZ53P[na] | (f & 2) | c | h
- elseif y == 5 then
- a = a ~ 255
- R[7] = a
- F = (F & 0xC5) | 0x12 | (a & 0x28)
- elseif y == 6 then
- F = (F & 0xC4) | 1 | (a & 0x28)
- else
- local c = F & 1
- F = (F & 0xC4) | (c << 4) | (a & 0x28) | (1 - c)
- end
- return 4
- end
- else
- local y = (op >> 3) & 7
- local z = op & 7
- if z == 0 then
- if cc(y) then
- PC = pop()
- return 11
- end
- return 5
- elseif z == 1 then
- local p = y >> 1
- if (y & 1) == 0 then
- local v = pop()
- if p == 3 then
- R[7] = v >> 8
- F = v & 255
- else
- setrp(p, v)
- end
- return 10
- end
- if p == 0 then
- PC = pop()
- return 10
- elseif p == 1 then
- for i = 0, 5 do
- R[i], S[i] = S[i], R[i]
- end
- return 4
- elseif p == 2 then
- PC = (R[4] << 8) | R[5]
- return prefixed and 8 or 4
- end
- SP = (R[4] << 8) | R[5]
- return prefixed and 10 or 6
- elseif z == 2 then
- local nn = fetch16()
- if cc(y) then PC = nn end
- return 10
- elseif z == 3 then
- if y == 0 then
- PC = fetch16()
- return 10
- elseif y == 2 then
- local n = fetch()
- portOut((R[7] << 8) | n, R[7])
- return 11
- elseif y == 3 then
- local n = fetch()
- R[7] = portIn((R[7] << 8) | n)
- return 11
- elseif y == 4 then
- local lo = M[SP]
- local hi = M[(SP + 1) & 0xFFFF]
- wr(SP, R[5])
- wr((SP + 1) & 0xFFFF, R[4])
- R[5], R[4] = lo, hi
- return prefixed and 23 or 19
- elseif y == 5 then
- R[2], R[4] = R[4], R[2]
- R[3], R[5] = R[5], R[3]
- return 4
- elseif y == 6 then
- IFF1 = false
- IFF2 = false
- return 4
- else
- IFF1 = true
- IFF2 = true
- eiDelay = 2
- return 4
- end
- elseif z == 4 then
- local nn = fetch16()
- if cc(y) then
- push(PC)
- PC = nn
- return 17
- end
- return 10
- elseif z == 5 then
- if (y & 1) == 0 then
- local p = y >> 1
- local v
- if p == 3 then
- v = (R[7] << 8) | F
- else
- v = getrp(p)
- end
- push(v)
- return 11
- end
- local nn = fetch16()
- push(PC)
- PC = nn
- return 17
- elseif z == 6 then
- alu(y, fetch())
- return 7
- else
- push(PC)
- PC = y << 3
- return 11
- end
- end
- end
- local function cbop()
- local op = fetchOp()
- local x = op >> 6
- local y = (op >> 3) & 7
- local z = op & 7
- local a, v
- if z == 6 then
- a = (R[4] << 8) | R[5]
- v = M[a]
- else
- v = R[z]
- end
- if x == 0 then
- v = rot(y, v)
- elseif x == 1 then
- local m = v & (1 << y)
- F = (F & 1)
- | 0x10
- | (v & 0x28)
- | ((m == 0) and 0x44 or (m & 0x80))
- return (z == 6) and 12 or 8
- elseif x == 2 then
- v = v & ~(1 << y)
- else
- v = v | (1 << y)
- end
- if z == 6 then
- wr(a, v)
- return 15
- end
- R[z] = v
- return 8
- end
- local function edop()
- local op = fetchOp()
- local x = op >> 6
- local y = (op >> 3) & 7
- local z = op & 7
- if x == 1 then
- local p = y >> 1
- if z == 0 then
- local v = portIn((R[0] << 8) | R[1])
- F = (F & 1) | SZ53P[v]
- if y ~= 6 then
- R[y] = v
- end
- return 12
- elseif z == 1 then
- portOut(
- (R[0] << 8) | R[1],
- (y == 6) and 0 or R[y]
- )
- return 12
- elseif z == 2 then
- local hl = (R[4] << 8) | R[5]
- local rr = getrp(p)
- local c = F & 1
- local r, res
- if (y & 1) == 0 then
- r = hl - rr - c
- res = r & 0xFFFF
- F = ((res >> 8) & 0xA8)
- | ((res == 0) and 0x40 or 0)
- | (((hl ~ rr ~ res) >> 8) & 0x10)
- | (((hl ~ rr) & (hl ~ res) & 0x8000) >> 13)
- | 2
- | ((r >> 16) & 1)
- else
- r = hl + rr + c
- res = r & 0xFFFF
- F = ((res >> 8) & 0xA8)
- | ((res == 0) and 0x40 or 0)
- | (((hl ~ rr ~ res) >> 8) & 0x10)
- | (((~(hl ~ rr)) & (hl ~ res) & 0x8000) >> 13)
- | ((r >> 16) & 1)
- end
- R[4], R[5] = res >> 8, res & 255
- return 15
- elseif z == 3 then
- local nn = fetch16()
- if (y & 1) == 0 then
- local v = getrp(p)
- wr(nn, v & 255)
- wr((nn + 1) & 0xFFFF, v >> 8)
- else
- setrp(
- p,
- M[nn] | (M[(nn + 1) & 0xFFFF] << 8)
- )
- end
- return 20
- elseif z == 4 then
- local v = R[7]
- local res = (-v) & 255
- F = SZ53[res]
- | 2
- | ((v ~ res) & 0x10)
- | ((v == 0x80) and 4 or 0)
- | ((v ~= 0) and 1 or 0)
- R[7] = res
- return 8
- elseif z == 5 then
- PC = pop()
- IFF1 = IFF2
- eiDelay = 0
- return 14
- elseif z == 6 then
- if y == 0 or y == 1 or y == 4 or y == 5 then
- IM = 0
- elseif y == 2 or y == 6 then
- IM = 1
- else
- IM = 2
- end
- return 8
- else
- if y == 0 then
- I = R[7]
- return 9
- elseif y == 1 then
- RR = R[7]
- return 9
- elseif y == 2 then
- R[7] = I
- F = (F & 1) | SZ53[I] | (IFF2 and 4 or 0)
- return 9
- elseif y == 3 then
- R[7] = RR
- F = (F & 1) | SZ53[RR] | (IFF2 and 4 or 0)
- return 9
- elseif y == 4 then
- local hl = (R[4] << 8) | R[5]
- local m = M[hl]
- local a = R[7]
- wr(hl, ((a & 15) << 4) | (m >> 4))
- a = (a & 0xF0) | (m & 15)
- R[7] = a
- F = (F & 1) | SZ53P[a]
- return 18
- elseif y == 5 then
- local hl = (R[4] << 8) | R[5]
- local m = M[hl]
- local a = R[7]
- wr(hl, ((m << 4) & 0xF0) | (a & 15))
- a = (a & 0xF0) | (m >> 4)
- R[7] = a
- F = (F & 1) | SZ53P[a]
- return 18
- end
- return 8
- end
- elseif x == 2 and y >= 4 and z <= 3 then
- local dec = (y & 1) == 1
- local rep = y >= 6
- local d = dec and -1 or 1
- local hl = (R[4] << 8) | R[5]
- if z == 0 then
- local de = (R[2] << 8) | R[3]
- local bc = (((R[0] << 8) | R[1]) - 1) & 0xFFFF
- local v = M[hl]
- wr(de, v)
- hl = (hl + d) & 0xFFFF
- de = (de + d) & 0xFFFF
- R[4], R[5] = hl >> 8, hl & 255
- R[2], R[3] = de >> 8, de & 255
- R[0], R[1] = bc >> 8, bc & 255
- local n = v + R[7]
- F = (F & 0xC1)
- | ((bc ~= 0) and 4 or 0)
- | (n & 8)
- | ((n & 2) << 4)
- if rep and bc ~= 0 then
- PC = (PC - 2) & 0xFFFF
- return 21
- end
- return 16
- elseif z == 1 then
- local bc = (((R[0] << 8) | R[1]) - 1) & 0xFFFF
- local v = M[hl]
- local res = (R[7] - v) & 255
- local hf = (R[7] ~ v ~ res) & 0x10
- local n = res - (hf >> 4)
- hl = (hl + d) & 0xFFFF
- R[4], R[5] = hl >> 8, hl & 255
- R[0], R[1] = bc >> 8, bc & 255
- F = (F & 1)
- | 2
- | (res & 0x80)
- | ((res == 0) and 0x40 or 0)
- | hf
- | ((bc ~= 0) and 4 or 0)
- | (n & 8)
- | ((n & 2) << 4)
- if rep and bc ~= 0 and res ~= 0 then
- PC = (PC - 2) & 0xFFFF
- return 21
- end
- return 16
- elseif z == 2 then
- local v = portIn((R[0] << 8) | R[1])
- wr(hl, v)
- R[0] = (R[0] - 1) & 255
- hl = (hl + d) & 0xFFFF
- R[4], R[5] = hl >> 8, hl & 255
- F = SZ53[R[0]] | 2
- if rep and R[0] ~= 0 then
- PC = (PC - 2) & 0xFFFF
- return 21
- end
- return 16
- else
- local v = M[hl]
- R[0] = (R[0] - 1) & 255
- portOut((R[0] << 8) | R[1], v)
- hl = (hl + d) & 0xFFFF
- R[4], R[5] = hl >> 8, hl & 255
- F = SZ53[R[0]] | 2
- if rep and R[0] ~= 0 then
- PC = (PC - 2) & 0xFFFF
- return 21
- end
- return 16
- end
- end
- return 8
- end
- local function indexedCB(ireg)
- local d = fetch()
- if d >= 128 then d = d - 256 end
- local op = fetch()
- local a = (ireg + d) & 0xFFFF
- local x = op >> 6
- local y = (op >> 3) & 7
- local z = op & 7
- local v = M[a]
- if x == 0 then
- v = rot(y, v)
- elseif x == 1 then
- local m = v & (1 << y)
- F = (F & 1)
- | 0x10
- | ((a >> 8) & 0x28)
- | ((m == 0) and 0x44 or (m & 0x80))
- return 20
- elseif x == 2 then
- v = v & ~(1 << y)
- else
- v = v | (1 << y)
- end
- wr(a, v)
- if z ~= 6 then
- R[z] = v
- end
- return 23
- end
- local function prefTotal(ins)
- local x = ins >> 6
- local y = (ins >> 3) & 7
- local z = ins & 7
- if x == 1 then
- return z == 6 or y == 6
- elseif x == 2 then
- return z == 6
- elseif x == 0 then
- if z == 1 or z == 3 then return true end
- return y == 6 and (z == 4 or z == 5 or z == 6)
- else
- if z == 1 then return y == 5 or y == 7 end
- if z == 3 then return y == 4 end
- end
- return false
- end
- local function prefix(op)
- local isIX = (op == 0xDD)
- local extra = 0
- while true do
- local n = M[PC]
- if n == 0xDD then
- fetchOp()
- isIX = true
- extra = extra + 4
- elseif n == 0xFD then
- fetchOp()
- isIX = false
- extra = extra + 4
- else
- break
- end
- end
- local ireg = isIX and IX or IY
- local nxt = M[PC]
- if nxt == 0xCB then
- fetchOp()
- return indexedCB(ireg) + extra
- end
- if nxt == 0xED then
- fetchOp()
- return edop() + 4 + extra
- end
- if nxt == 0xEB then
- fetchOp()
- R[2], R[4] = R[4], R[2]
- R[3], R[5] = R[5], R[3]
- return 8 + extra
- end
- if nxt == 0xD9 then
- fetchOp()
- for i = 0, 5 do
- R[i], S[i] = S[i], R[i]
- end
- return 8 + extra
- end
- local ignored =
- nxt == 0x00 or nxt == 0x08 or nxt == 0x10 or nxt == 0x18
- or nxt == 0x20 or nxt == 0x28 or nxt == 0x30 or nxt == 0x38
- or nxt == 0x76
- or nxt == 0xC3 or nxt == 0xC9
- or nxt == 0xCD
- or nxt == 0xC0 or nxt == 0xC8 or nxt == 0xD0 or nxt == 0xD8
- or nxt == 0xE0 or nxt == 0xE8 or nxt == 0xF0 or nxt == 0xF8
- or nxt == 0xC7 or nxt == 0xCF or nxt == 0xD7 or nxt == 0xDF
- or nxt == 0xE7 or nxt == 0xEF or nxt == 0xF7 or nxt == 0xFF
- or nxt == 0xF3 or nxt == 0xFB
- or nxt == 0x27 or nxt == 0x2F or nxt == 0x37 or nxt == 0x3F
- if ignored then
- return exec(fetchOp()) + 4 + extra
- end
- sh, sl = R[4], R[5]
- R[4] = ireg >> 8
- R[5] = ireg & 255
- prefixed = true
- local instruction = fetchOp()
- local t = exec(instruction)
- prefixed = false
- local nv = (R[4] << 8) | R[5]
- if isIX then
- IX = nv
- else
- IY = nv
- end
- R[4], R[5] = sh, sl
- if not prefTotal(instruction) then
- t = t + 4
- end
- return t + extra
- end
- local function tapTrap()
- local want = R[7]
- local isLoad = (F & 1) ~= 0
- local de = (R[2] << 8) | R[3]
- local ok = false
- while tapePos <= #tapBlocks do
- local blk = tapBlocks[tapePos]
- tapePos = tapePos + 1
- if #blk > 0 and blk:byte(1) == want then
- if isLoad then
- local n = #blk - 2
- local cnt = math.min(n, de)
- for i = 0, cnt - 1 do
- wr((IX + i) & 0xFFFF, blk:byte(2 + i))
- end
- ok = (n == de)
- IX = (IX + cnt) & 0xFFFF
- de = de - cnt
- else
- ok = true
- end
- break
- end
- end
- R[2], R[3] = de >> 8, de & 255
- if ok then
- F = F | 1
- else
- F = F & 0xFE
- end
- PC = pop()
- IFF1 = true
- IFF2 = true
- eiDelay = 0
- return 10
- end
- local function step()
- if PC == 0x0556 and tapBlocks then
- return tapTrap()
- end
- local op = fetchOp()
- if op == 0xCB then
- return cbop()
- elseif op == 0xED then
- return edop()
- elseif op == 0xDD or op == 0xFD then
- return prefix(op)
- end
- return exec(op)
- end
- local function resetMachine()
- for i = 0x4000, 0xFFFF do
- M[i] = 0
- end
- for i = 0, 7 do
- R[i] = 0
- S[i] = 0
- end
- R[7] = 0xFF
- S[7] = 0xFF
- F = 0xFF
- F2 = 0xFF
- PC = 0
- SP = 0xFFFF
- IX = 0xFFFF
- IY = 0xFFFF
- I = 0
- RR = 0
- IFF1 = false
- IFF2 = false
- IM = 0
- halted = false
- eiDelay = 0
- border = 7
- borderDirty = true
- spk = 0
- tgN = 0
- tapePos = 1
- for i = 0, 1535 do
- dirty[i] = true
- end
- anyDirty = true
- end
- local function loadSNA(d)
- local function b(i)
- return d:byte(i + 1)
- end
- I = b(0)
- S[5], S[4] = b(1), b(2)
- S[3], S[2] = b(3), b(4)
- S[1], S[0] = b(5), b(6)
- F2, S[7] = b(7), b(8)
- R[5], R[4] = b(9), b(10)
- R[3], R[2] = b(11), b(12)
- R[1], R[0] = b(13), b(14)
- IY = b(15) | (b(16) << 8)
- IX = b(17) | (b(18) << 8)
- IFF2 = (b(19) & 4) ~= 0
- IFF1 = IFF2
- RR = b(20)
- F, R[7] = b(21), b(22)
- SP = b(23) | (b(24) << 8)
- IM = b(25) & 3
- border = b(26) & 7
- for i = 0, 49151 do
- M[0x4000 + i] = d:byte(28 + i)
- end
- PC = pop()
- halted = false
- eiDelay = 0
- borderDirty = true
- for i = 0, 1535 do
- dirty[i] = true
- end
- anyDirty = true
- end
- local function z80Unpack(d, pos, last, want)
- local out, n, o = {}, 0, 0
- while o < want and pos <= last do
- local b = d:byte(pos)
- if b == 0xED and pos + 3 <= last and d:byte(pos + 1) == 0xED then
- local cnt = d:byte(pos + 2)
- local v = d:byte(pos + 3)
- n = n + 1
- out[n] = string.rep(string.char(v), cnt)
- o = o + cnt
- pos = pos + 4
- else
- n = n + 1
- out[n] = string.char(b)
- o = o + 1
- pos = pos + 1
- end
- end
- local s = table.concat(out)
- if #s < want then
- s = s .. string.rep("\0", want - #s)
- end
- return s:sub(1, want)
- end
- local function loadZ80(d)
- local function b(i)
- return d:byte(i + 1) or 0
- end
- R[7] = b(0)
- F = b(1)
- R[1], R[0] = b(2), b(3)
- R[5], R[4] = b(4), b(5)
- local pc = b(6) | (b(7) << 8)
- SP = b(8) | (b(9) << 8)
- I = b(10)
- local f12 = b(12)
- if f12 == 255 then f12 = 1 end
- RR = (b(11) & 0x7F) | ((f12 & 1) << 7)
- border = (f12 >> 1) & 7
- local compressed = (f12 & 0x20) ~= 0
- R[3], R[2] = b(13), b(14)
- S[1], S[0] = b(15), b(16)
- S[3], S[2] = b(17), b(18)
- S[5], S[4] = b(19), b(20)
- S[7], F2 = b(21), b(22)
- IY = b(23) | (b(24) << 8)
- IX = b(25) | (b(26) << 8)
- IFF1 = b(27) ~= 0
- IFF2 = b(28) ~= 0
- IM = b(29) & 3
- if pc ~= 0 then
- local mem
- if compressed then
- mem = z80Unpack(d, 31, #d, 49152)
- else
- mem = d:sub(31, 30 + 49152)
- end
- for i = 0, 49151 do
- M[0x4000 + i] = mem:byte(i + 1) or 0
- end
- PC = pc
- else
- local hlen = b(30) | (b(31) << 8)
- PC = b(32) | (b(33) << 8)
- local mode = b(34)
- if (hlen == 23 and mode >= 3) or (hlen ~= 23 and mode >= 4) then
- error("z80: only 48K snapshots are supported")
- end
- local pos = 33 + hlen
- while pos + 2 <= #d do
- local len = d:byte(pos) | (d:byte(pos + 1) << 8)
- local page = d:byte(pos + 2)
- pos = pos + 3
- local mem
- if len == 0xFFFF then
- mem = d:sub(pos, pos + 16384)
- pos = pos + 16384
- else
- mem = z80Unpack(d, pos, pos + len - 1, 16384)
- pos = pos + len
- end
- local base
- if page == 8 then
- base = 0x4000
- elseif page == 4 then
- base = 0x8000
- elseif page == 5 then
- base = 0xC000
- end
- if base then
- for i = 0, 16383 do
- M[base + i] = mem:byte(i + 1) or 0
- end
- end
- end
- end
- halted = false
- eiDelay = 0
- borderDirty = true
- for i = 0, 1535 do dirty[i] = true end
- anyDirty = true
- end
- local function loadSCR(d)
- for i = 0, 6911 do
- M[0x4000 + i] = d:byte(i + 1)
- end
- for i = 0, 1535 do
- dirty[i] = true
- end
- anyDirty = true
- end
- local function loadBIN(d)
- local addr = tonumber(os.getenv("BIN_ADDR") or "0x8000") or 0x8000
- for i = 1, #d do
- M[(addr + i - 1) & 0xFFFF] = d:byte(i)
- end
- for i = 0, 1535 do dirty[i] = true end
- anyDirty = true
- end
- local LINE = {}
- for y = 0, 191 do
- LINE[y] =
- 0x4000
- | ((y & 0xC0) << 5)
- | ((y & 7) << 8)
- | ((y & 0x38) << 2)
- end
- local BL = {1, 2, 4, 0x40}
- local BR = {8, 0x10, 0x20, 0x80}
- local PX = {}
- for k = 1, 4 do
- PX[k] = {}
- for pair = 0, 3 do
- PX[k][pair] =
- (((pair & 2) ~= 0) and BL[k] or 0)
- | (((pair & 1) ~= 0) and BR[k] or 0)
- end
- end
- local PX1, PX2, PX3, PX4 =
- PX[1], PX[2], PX[3], PX[4]
- local uchar
- do
- local ok, unicode = pcall(require, "unicode")
- if ok and unicode and unicode.char then
- uchar = unicode.char
- else
- uchar = utf8.char
- end
- end
- local curFG, curBG = -1, -1
- local function cell(by, bx)
- local y0 = by << 2
- local attr =
- M[0x5800 + ((y0 >> 3) << 5) + bx]
- local ink = attr & 7
- local paper = (attr >> 3) & 7
- if (attr & 0x80) ~= 0 and flashOn then
- ink, paper = paper, ink
- end
- if (attr & 0x40) ~= 0 then
- ink = ink + 8
- paper = paper + 8
- end
- local b0 = M[LINE[y0] + bx]
- local b1 = M[LINE[y0 + 1] + bx]
- local b2 = M[LINE[y0 + 2] + bx]
- local b3 = M[LINE[y0 + 3] + bx]
- local s = uchar(
- 0x2800
- | PX1[(b0 >> 6) & 3]
- | PX2[(b1 >> 6) & 3]
- | PX3[(b2 >> 6) & 3]
- | PX4[(b3 >> 6) & 3],
- 0x2800
- | PX1[(b0 >> 4) & 3]
- | PX2[(b1 >> 4) & 3]
- | PX3[(b2 >> 4) & 3]
- | PX4[(b3 >> 4) & 3],
- 0x2800
- | PX1[(b0 >> 2) & 3]
- | PX2[(b1 >> 2) & 3]
- | PX3[(b2 >> 2) & 3]
- | PX4[(b3 >> 2) & 3],
- 0x2800
- | PX1[b0 & 3]
- | PX2[b1 & 3]
- | PX3[b2 & 3]
- | PX4[b3 & 3]
- )
- return ink, paper, s
- end
- local function flush(by, x0, fg, bg, str)
- if fg ~= curFG then
- gpu.setForeground(fg, true)
- curFG = fg
- end
- if bg ~= curBG then
- gpu.setBackground(bg, true)
- curBG = bg
- end
- gpu.set(
- ox + x0 * 4 + 1,
- oy + by + 1,
- str
- )
- end
- local function drawBorder()
- borderDirty = false
- if ox == 0 then return end
- if curBG ~= border then
- gpu.setBackground(border, true)
- curBG = border
- end
- gpu.fill(1, 1, 160, oy, " ")
- gpu.fill(1, 51 - oy, 160, oy, " ")
- gpu.fill(1, oy + 1, ox, 48, " ")
- gpu.fill(161 - ox, oy + 1, ox, 48, " ")
- end
- local function render()
- if borderDirty then
- drawBorder()
- end
- if not anyDirty then
- return
- end
- anyDirty = false
- for by = 0, 47 do
- local base = by * 32
- local bx = 0
- while bx < 32 do
- if dirty[base + bx] then
- local x0 = bx
- local fg, bg, str = cell(by, bx)
- dirty[base + bx] = nil
- bx = bx + 1
- while bx < 32 and dirty[base + bx] do
- local f2, b2, s2 = cell(by, bx)
- if f2 ~= fg or b2 ~= bg then
- break
- end
- dirty[base + bx] = nil
- str = str .. s2
- bx = bx + 1
- end
- flush(by, x0, fg, bg, str)
- else
- bx = bx + 1
- end
- end
- end
- end
- local function markFlash()
- for n = 0, 767 do
- if (M[0x5800 + n] & 0x80) ~= 0 then
- local k = (n >> 5) * 64 + (n & 31)
- dirty[k] = true
- dirty[k + 32] = true
- anyDirty = true
- end
- end
- end
- local CAPS = {{0, 0}}
- local SYM = {{7, 1}}
- local KEYS = {
- [42] = CAPS,
- [54] = SYM,
- [29] = SYM,
- [157] = SYM,
- [44] = {{0,1}},
- [45] = {{0,2}},
- [46] = {{0,3}},
- [47] = {{0,4}},
- [30] = {{1,0}},
- [31] = {{1,1}},
- [32] = {{1,2}},
- [33] = {{1,3}},
- [34] = {{1,4}},
- [16] = {{2,0}},
- [17] = {{2,1}},
- [18] = {{2,2}},
- [19] = {{2,3}},
- [20] = {{2,4}},
- [2] = {{3,0}},
- [3] = {{3,1}},
- [4] = {{3,2}},
- [5] = {{3,3}},
- [6] = {{3,4}},
- [11] = {{4,0}},
- [10] = {{4,1}},
- [9] = {{4,2}},
- [8] = {{4,3}},
- [7] = {{4,4}},
- [25] = {{5,0}},
- [24] = {{5,1}},
- [23] = {{5,2}},
- [22] = {{5,3}},
- [21] = {{5,4}},
- [28] = {{6,0}},
- [38] = {{6,1}},
- [37] = {{6,2}},
- [36] = {{6,3}},
- [35] = {{6,4}},
- [57] = {{7,0}},
- [50] = {{7,2}},
- [49] = {{7,3}},
- [48] = {{7,4}},
- [14] = {CAPS[1], {4,0}},
- [203] = {CAPS[1], {3,4}},
- [208] = {CAPS[1], {4,4}},
- [200] = {CAPS[1], {4,3}},
- [205] = {CAPS[1], {4,2}},
- [51] = {SYM[1], {7,3}},
- [52] = {SYM[1], {7,2}},
- [53] = {SYM[1], {0,4}},
- [39] = {SYM[1], {5,1}},
- [12] = {SYM[1], {6,3}},
- [13] = {SYM[1], {6,1}},
- [40] = {SYM[1], {4,3}},
- }
- local function pressList(list)
- for _, k in ipairs(list) do
- local idx = k[1] * 8 + k[2]
- local n = (kcount[idx] or 0) + 1
- kcount[idx] = n
- if n == 1 then
- kb[k[1]] = kb[k[1]] & ~(1 << k[2])
- end
- end
- end
- local function releaseList(list)
- for _, k in ipairs(list) do
- local idx = k[1] * 8 + k[2]
- local n = (kcount[idx] or 0) - 1
- if n <= 0 then
- kcount[idx] = 0
- kb[k[1]] = kb[k[1]] | (1 << k[2])
- else
- kcount[idx] = n
- end
- end
- end
- local frames = 0
- local running = true
- local muted = false
- local resetAudio
- local adjustVolume
- local downAt = {}
- local pendingUp = {}
- local typeQ
- local typeI = 0
- local typeT = 0
- local LOAD_SEQ = {
- {{6,3}},
- {SYM[1], {5,0}},
- {SYM[1], {5,0}},
- {{6,0}},
- }
- local function typeTick()
- if not typeQ then return end
- typeT = typeT + 1
- local e = typeQ[typeI]
- if typeT == 1 then
- pressList(e)
- elseif typeT == 5 then
- releaseList(e)
- elseif typeT >= 8 then
- typeT = 0
- typeI = typeI + 1
- if typeI > #typeQ then
- typeQ = nil
- end
- end
- end
- local function handle(name, _, _, code)
- if name == "key_down" then
- if code == 88 then
- running = false
- return
- end
- if code == 63 then
- resetMachine()
- return
- end
- if code == 61 then
- typeQ = LOAD_SEQ
- typeI = 1
- typeT = 0
- return
- end
- if code == 62 then
- tapePos = 1
- return
- end
- if code == 64 then
- if adjustVolume then adjustVolume(-0.1) end
- return
- end
- if code == 65 then
- if adjustVolume then adjustVolume(0.1) end
- return
- end
- if code == 60 then
- muted = not muted
- if resetAudio then resetAudio() end
- return
- end
- local list = KEYS[code]
- if list then
- if not downAt[code] then
- downAt[code] = frames
- pressList(list)
- end
- pendingUp[code] = nil
- end
- elseif name == "key_up" then
- local t = downAt[code]
- if t then
- if frames - t >= KEY_HOLD then
- releaseList(KEYS[code])
- downAt[code] = nil
- else
- pendingUp[code] = t + KEY_HOLD
- end
- end
- elseif name == "interrupted" then
- running = false
- end
- end
- local function pump(timeout)
- local s, a, b, c = computer.pullSignal(timeout)
- while s and running do
- handle(s, a, b, c)
- s, a, b, c = computer.pullSignal(0)
- end
- end
- local SNDCFG = {spFs = 16384, spN = 1024, polyMin = 0.02, polyRel = 0.06, polyMax = 8, gain = 0.85, tilt = 0.2, maxHz = 15000}
- local SP_FS = SNDCFG.spFs or 16384
- local SP_N = SNDCFG.spN or 1024
- local SP_M = SP_N // 2
- local SP_TS = CPU_HZ / SP_FS
- local SP_DF = SP_FS / SP_N
- local SP_NYQ = SP_FS / 2
- local SP_KMIN = math.max(2, math.ceil(45 / SP_DF))
- local spRing, spW, spS, spLast, spAcc, spLvl = {}, 0, 0, 0, 0, 0
- local spHann, spBR, spTwr, spTwi, spPwr, spPwi = {}, {}, {}, {}, {}, {}
- local spX, spRe, spIm, spAmp, spMark = {}, {}, {}, {}, {}
- local spPrev = {}
- local spStamp = 0
- local spCount = 0
- local spCache = nil
- do
- for i = 1, SP_N do
- spHann[i] = 0.5 - 0.5 * math.cos(2 * math.pi * (i - 1) / SP_N)
- spX[i] = 0
- spRing[i] = 0
- end
- local bits = 0
- while (1 << bits) < SP_M do bits = bits + 1 end
- for m = 0, SP_M - 1 do
- local r, x = 0, m
- for _ = 1, bits do
- r = (r << 1) | (x & 1)
- x = x >> 1
- end
- spBR[m + 1] = r + 1
- spRe[m + 1] = 0
- spIm[m + 1] = 0
- end
- for t = 0, SP_M // 2 - 1 do
- spTwr[t + 1] = math.cos(2 * math.pi * t / SP_M)
- spTwi[t + 1] = -math.sin(2 * math.pi * t / SP_M)
- end
- for k = 0, SP_M do
- spPwr[k + 1] = math.cos(2 * math.pi * k / SP_N)
- spPwi[k + 1] = -math.sin(2 * math.pi * k / SP_N)
- spAmp[k + 1] = 0
- spMark[k + 1] = 0
- end
- end
- local function spInit(t)
- spW = 0
- spS = t
- spLast = t
- spAcc = 0
- spPrev = {}
- spCache = nil
- spCount = 0
- for i = 1, SP_N do spRing[i] = 0 end
- end
- local function spAdvance(t)
- if t <= spLast then return end
- local lvl = spLvl
- while spS + SP_TS <= t do
- local e = spS + SP_TS
- local v = (spAcc + lvl * (e - spLast)) / SP_TS
- spW = spW + 1
- spRing[(spW - 1) % SP_N + 1] = v
- spS = e
- spLast = e
- spAcc = 0
- end
- spAcc = spAcc + lvl * (t - spLast)
- spLast = t
- end
- local function spFeed(fe, tT, tL, tN)
- for i = 1, tN do
- spAdvance(tT[i])
- spLvl = tL[i]
- end
- spAdvance(fe)
- end
- local function spSpectrum()
- local N, M = SP_N, SP_M
- local base = spW - N
- local sum = 0
- for i = 1, N do
- local idx = base + i
- local v = 0
- if idx >= 1 then v = spRing[(idx - 1) % N + 1] end
- spX[i] = v
- sum = sum + v
- end
- local mean = sum / N
- for m = 0, M - 1 do
- local j = spBR[m + 1]
- local i0 = 2 * m + 1
- spRe[j] = (spX[i0] - mean) * spHann[i0]
- spIm[j] = (spX[i0 + 1] - mean) * spHann[i0 + 1]
- end
- local re, im = spRe, spIm
- local size = 2
- while size <= M do
- local half = size >> 1
- local step = M // size
- for st = 0, M - 1, size do
- local tw = 1
- for k = st + 1, st + half do
- local wr, wi = spTwr[tw], spTwi[tw]
- local b = k + half
- local br, bi = re[b], im[b]
- local xr = br * wr - bi * wi
- local xi = br * wi + bi * wr
- local ar, ai = re[k], im[k]
- re[b] = ar - xr
- im[b] = ai - xi
- re[k] = ar + xr
- im[k] = ai + xi
- tw = tw + step
- end
- end
- size = size << 1
- end
- local scale = 4 / N
- for k = 0, M do
- local ka = k % M
- local kb = (M - k) % M
- local zr, zi = re[ka + 1], im[ka + 1]
- local cr, ci = re[kb + 1], -im[kb + 1]
- local er, ei = (zr + cr) * 0.5, (zi + ci) * 0.5
- local orr, oi = (zi - ci) * 0.5, -(zr - cr) * 0.5
- local wr, wi = spPwr[k + 1], spPwi[k + 1]
- local xr = er + orr * wr - oi * wi
- local xi = ei + orr * wi + oi * wr
- spAmp[k + 1] = math.sqrt(xr * xr + xi * xi) * scale
- end
- end
- local function spBand(f, tol)
- local k0 = math.floor(f * (1 - tol) / SP_DF)
- local k1 = math.ceil(f * (1 + tol) / SP_DF)
- if k0 < 1 then k0 = 1 end
- if k1 > SP_M then k1 = SP_M end
- local best = 0
- for k = k0, k1 do
- local a = spAmp[k + 1]
- if a > best then best = a end
- end
- return best
- end
- local SP_FUND = {sq = 4 / math.pi, tri = 8 / (math.pi * math.pi), sin = 1}
- local SP_HARM = {
- sq = function(h) return (h % 2 == 1) and 1 / h or 0 end,
- tri = function(h) return (h % 2 == 1) and 1 / (h * h) or 0 end,
- sin = function(h) return 0 end,
- }
- local function spVoices()
- spSpectrum()
- local amp = spAmp
- local kmax = SP_M - 1
- local top = 0
- local tot = 0
- for k = SP_KMIN, kmax do
- local a = amp[k + 1]
- tot = tot + a * a
- if a > top then top = a end
- end
- if top < (SNDCFG.polyMin or 0.02) or tot <= 0 then return nil, 0 end
- local thr = math.max(SNDCFG.polyMin or 0.02, (SNDCFG.polyRel or 0.06) * top)
- local cands = {}
- for k = SP_KMIN, kmax do
- local a = amp[k + 1]
- if a >= thr * 0.6 and a >= amp[k] and a > amp[k + 2] then
- local al, ac, ar = amp[k], a, amp[k + 2]
- local la, lb, lc = math.log(math.max(al, 1e-9)), math.log(ac),
- math.log(math.max(ar, 1e-9))
- local den = la - 2 * lb + lc
- local d = 0
- if den < -1e-9 then d = 0.5 * (la - lc) / den end
- if d > 0.5 then d = 0.5 elseif d < -0.5 then d = -0.5 end
- local f = (k + d) * SP_DF
- local pa = math.exp(lb - 0.25 * (la - lc) * d)
- local keep = pa >= thr
- if not keep then
- for i = 1, #spPrev do
- if math.abs(f / spPrev[i] - 1) < 0.04 then keep = true break end
- end
- end
- if keep then cands[#cands + 1] = {f = f, a = pa, k = k} end
- end
- end
- if #cands == 0 then return nil, 0 end
- table.sort(cands, function(x, y) return x.a > y.a end)
- spStamp = spStamp + 1
- local pe = 0
- for i = 1, math.min(#cands, 10) do
- local k = cands[i].k
- for j = math.max(SP_KMIN, k - 2), math.min(kmax, k + 2) do
- if spMark[j + 1] ~= spStamp then
- spMark[j + 1] = spStamp
- pe = pe + amp[j + 1] * amp[j + 1]
- end
- end
- end
- local conc = pe / tot
- local maxV = math.min(8, SNDCFG.polyMax or 8)
- local voices = {}
- for _, p in ipairs(cands) do
- local harmonic = false
- for _, v in ipairs(voices) do
- local h = math.floor(p.f / v.f + 0.5)
- if h >= 2 and h <= 8 then
- local near = math.abs(p.f / (h * v.f) - 1) < 0.03
- or math.abs(p.f - h * v.f) < 1.3 * SP_DF
- if near and p.a <= 1.6 * v.a / h then
- v.harm[#v.harm + 1] = {h = h, f = p.f, a = p.a}
- harmonic = true
- break
- end
- end
- end
- if not harmonic and #voices < maxV then
- voices[#voices + 1] = {f = p.f, a = p.a, harm = {}}
- end
- end
- local g = SNDCFG.gain or 0.85
- local tilt = SNDCFG.tilt or 0.2
- local out, extras = {}, {}
- local sumv = 0
- for _, v in ipairs(voices) do
- local r2 = (2 * v.f < SP_NYQ) and spBand(2 * v.f, 0.03) / v.a or 0
- local r3 = (3 * v.f < SP_NYQ) and spBand(3 * v.f, 0.03) / v.a or 1 / 3
- local wave
- if r3 < 0.05 and r2 < 0.1 then
- wave = "sin"
- elseif r3 < 0.2 and r2 < 0.2 then
- wave = "tri"
- else
- wave = "sq"
- end
- local fm = SP_HARM[wave]
- local lift = math.min(1.6, math.max(0.85, (v.f / 250) ^ tilt))
- local vol = v.a * 2 * g * lift / SP_FUND[wave]
- out[#out + 1] = {v.f, vol, wave, v.a}
- sumv = sumv + vol
- for _, hm in ipairs(v.harm) do
- local res = hm.a - fm(hm.h) * v.a
- if res >= math.max(SNDCFG.polyMin or 0.02, 0.12 * v.a)
- and hm.f < SNDCFG.maxHz then
- extras[#extras + 1] = {hm.f, res * 2 * g, "sin", res}
- end
- end
- end
- table.sort(extras, function(x, y) return x[4] > y[4] end)
- for i = 1, #extras do
- if #out >= 8 then break end
- out[#out + 1] = extras[i]
- sumv = sumv + extras[i][2]
- end
- local sc = 1
- if sumv > 1.15 then sc = 1.15 / sumv end
- for _, o in ipairs(out) do
- o[2] = math.min(1, o[2] * sc)
- end
- spPrev = {}
- for i = 1, #out do spPrev[i] = out[i][1] end
- return out, conc
- end
- local masterVol = MASTER_VOLUME
- local chs = { held = false, cw = {}, cv = {}, cf = {}, co = {} }
- local function resetChannels()
- chs.cw, chs.cv, chs.cf, chs.co = {}, {}, {}, {}
- chs.init = false
- chs.held = false
- end
- resetChannels()
- local function cardClear()
- if snd and snd.clear then
- pcall(function() snd.clear() end)
- end
- if chs.held and snd then
- pcall(function()
- for c = 1, 8 do snd.close(c) end
- snd.process()
- end)
- end
- resetChannels()
- end
- resetAudio = function()
- tgN = 0
- spInit(ts)
- cardClear()
- end
- adjustVolume = function(delta)
- if not snd then return end
- masterVol = math.max(0, math.min(1, masterVol + delta))
- pcall(function() snd.setTotalVolume(masterVol) end)
- end
- local function initSound()
- if not snd then return end
- pcall(function() snd.setTotalVolume(masterVol) end)
- spInit(0)
- end
- local function closeSound()
- if not snd then return end
- pcall(function()
- for c = 1, 8 do snd.close(c) end
- snd.process()
- end)
- end
- local WAVE_MAP = {
- sq = 1,
- sin = 2,
- tri = 3
- }
- local function analyseBeeper(fs, fe)
- if not snd or muted then
- tgN = 0
- return
- end
- spFeed(fe, tgT, tgL, tgN)
- tgN = 0
- end
- local function submitAudio()
- if not snd or muted then return end
- local voices, conc = spVoices()
- if not voices or #voices == 0 then
- if chs.held then
- pcall(function()
- for c = 1, 8 do snd.close(c) end
- snd.process()
- end)
- resetChannels()
- end
- return
- end
- pcall(function()
- local modes = snd.modes or {}
- local waveTypes = {
- sq = modes.square or 1,
- sin = modes.sine or modes.square or 1,
- tri = modes.triangle or modes.square or 1
- }
- if not chs.init then
- for c = 1, 8 do
- snd.close(c)
- pcall(snd.setADSR, c, 1, 1, 1, 1)
- end
- chs.init = true
- end
- local usedChannels = #voices
- for c = 1, usedChannels do
- local v = voices[c]
- local freq = v[1]
- local vol = v[2]
- local waveStr = v[3]
- local waveType = waveTypes[waveStr] or 1
- if chs.cw[c] ~= waveType then
- snd.setWave(c, waveType)
- chs.cw[c] = waveType
- end
- if not chs.cv[c] or math.abs(chs.cv[c] - vol) > 0.02 then
- snd.setVolume(c, vol)
- chs.cv[c] = vol
- end
- local fi = math.max(1, math.floor(freq + 0.5))
- if chs.cf[c] ~= fi then
- snd.setFrequency(c, fi)
- chs.cf[c] = fi
- end
- if not chs.co[c] then
- snd.open(c)
- chs.co[c] = true
- end
- end
- for c = usedChannels + 1, 8 do
- if chs.co[c] then
- snd.close(c)
- chs.co[c] = false
- end
- end
- snd.delay(20)
- snd.process()
- chs.held = true
- end)
- end
- local saved = {}
- local function setupGPU()
- local mw, mh = gpu.maxResolution()
- saved.w, saved.h = gpu.getResolution()
- saved.depth = gpu.getDepth()
- saved.fg, saved.fgp = gpu.getForeground()
- saved.bg, saved.bgp = gpu.getBackground()
- if mw >= 160 and mh >= 50 then
- gpu.setResolution(160, 50)
- ox, oy = 16, 1
- elseif mw >= 128 and mh >= 48 then
- gpu.setResolution(128, 48)
- ox, oy = 0, 0
- else
- error(
- "a Tier 3 GPU and screen (at least 128x48 characters) are required"
- )
- end
- gpu.setDepth(gpu.maxDepth())
- saved.pal = {}
- for c = 0, 15 do
- local v = (c >= 8) and 0xFF or 0xD7
- local cc = c & 7
- local r = ((cc & 2) ~= 0) and v or 0
- local g = ((cc & 4) ~= 0) and v or 0
- local b = ((cc & 1) ~= 0) and v or 0
- saved.pal[c] =
- gpu.setPaletteColor(
- c,
- (r << 16) | (g << 8) | b
- )
- end
- local w, h = gpu.getResolution()
- gpu.setBackground(7, true)
- curBG = 7
- gpu.fill(1, 1, w, h, " ")
- curFG = -1
- end
- local function restoreGPU()
- pcall(function()
- if saved.pal then
- for c = 0, 15 do
- if saved.pal[c] then
- gpu.setPaletteColor(c, saved.pal[c])
- end
- end
- end
- if saved.depth then
- gpu.setDepth(saved.depth)
- end
- if saved.w then
- gpu.setResolution(saved.w, saved.h)
- end
- if saved.fg then
- gpu.setForeground(saved.fg, saved.fgp)
- end
- if saved.bg then
- gpu.setBackground(saved.bg, saved.bgp)
- end
- local w, h = gpu.getResolution()
- gpu.fill(1, 1, w, h, " ")
- end)
- end
- local function serviceKeyboard()
- for code, f in pairs(pendingUp) do
- if frames >= f then
- local list = KEYS[code]
- if list then
- releaseList(list)
- end
- downAt[code] = nil
- pendingUp[code] = nil
- end
- end
- end
- local function acceptInterrupt()
- halted = false
- IFF1 = false
- IFF2 = false
- incR()
- if IM == 2 then
- push(PC)
- local v = (I << 8) | 0xFF
- PC =
- M[v]
- | (M[(v + 1) & 0xFFFF] << 8)
- return 19
- end
- push(PC)
- PC = 0x38
- return 13
- end
- local function main()
- setupGPU()
- initSound()
- resetMachine()
- local haveSnapshot = false
- if snaData then
- loadSNA(snaData)
- haveSnapshot = true
- elseif z80Data then
- loadZ80(z80Data)
- haveSnapshot = true
- elseif scrData then
- loadSCR(scrData)
- elseif binData then
- loadBIN(binData)
- haveSnapshot = true
- end
- local autoLoad = tapBlocks ~= nil and not haveSnapshot
- frameStart = ts
- local nextT = computer.uptime()
- local lastPump = nextT
- while running do
- frames = frames + 1
- local frameEnd = frameStart + FRAME_TS
- local intPending = true
- local intEnd = frameStart + 32
- while ts < frameEnd do
- if intPending then
- if ts >= intEnd then
- intPending = false
- elseif IFF1 and eiDelay == 0 then
- intPending = false
- ts = ts + acceptInterrupt()
- end
- end
- if halted then
- ts = frameEnd
- else
- ts = ts + step()
- if eiDelay > 0 then
- eiDelay = eiDelay - 1
- end
- end
- end
- analyseBeeper(frameStart, frameEnd)
- frameStart = frameEnd
- if (frames & 15) == 0 then
- flashOn = not flashOn
- markFlash()
- end
- if autoLoad and frames == AUTOLOAD_FRAME then
- typeQ = LOAD_SEQ
- typeI = 1
- typeT = 0
- end
- typeTick()
- serviceKeyboard()
- if frames % RENDER_EVERY == 0 then
- render()
- end
- submitAudio()
- local now = computer.uptime()
- if now < nextT then
- repeat
- pump(nextT - now)
- now = computer.uptime()
- until now >= nextT or not running
- lastPump = now
- elseif now - lastPump >= PUMP_INTERVAL then
- pump(0)
- lastPump = computer.uptime()
- end
- nextT = nextT + FRAME_TS / CPU_HZ
- if nextT < now - 0.1 then
- nextT = now
- end
- end
- end
- local ok, err = xpcall(main, debug.traceback)
- closeSound()
- restoreGPU()
- if not ok then
- io.stderr:write(tostring(err) .. "\n")
- end
Add Comment
Please, Sign In to add comment