Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- local component = require("component")
- local event = require("event")
- local computer = require("computer")
- local bit = require("bit32")
- if not component.isAvailable("sound") then
- error("A Computronics Sound Card is required.")
- end
- local sound = component.sound
- ----------------------------------------------------------------
- -- Configuration
- ----------------------------------------------------------------
- local RAM_SIZE = 65536
- local SID_BASE = 0xD400
- local SID_END = 0xD41F
- local MAX_SIDS = 2
- local VOICES_PER_SID = 3
- local TOTAL_VOICES = MAX_SIDS * VOICES_PER_SID
- local PAL_HZ = 50
- local NTSC_HZ = 60
- local PAL_CLOCK = 985248
- local NTSC_CLOCK = 1022727
- local SID_FREQ_DIV = 16777216
- local DEFAULT_VOLUME = 0.55
- local MIN_VOLUME = 0.0
- local MAX_VOLUME = 1.0
- local MAX_INSTRUCTIONS_PER_CALL = 200000
- local MAX_INSTRUCTIONS_PER_IRQ = 200000
- -- Common C64 KERNAL IRQ exit.
- -- We emulate the effect of reaching it instead of requiring
- -- the KERNAL ROM to actually be present.
- local KERNAL_IRQ_EXIT = 0xEA31
- local WAVE_NOISE = sound.modes.noise
- local WAVE_SQUARE = sound.modes.square
- local WAVE_SAW = sound.modes.sawtooth
- local WAVE_TRIANGLE = sound.modes.triangle
- ----------------------------------------------------------------
- -- Basic helpers
- ----------------------------------------------------------------
- local function u8(x)
- x = x % 256
- if x < 0 then
- x = x + 256
- end
- return x
- end
- local function u16(x)
- x = x % 65536
- if x < 0 then
- x = x + 65536
- end
- return x
- end
- local function lo(x)
- return bit.band(x, 0xFF)
- end
- local function hi(x)
- return bit.band(bit.rshift(x, 8), 0xFF)
- end
- local function signed8(x)
- if x >= 128 then
- return x - 256
- end
- return x
- end
- local function clamp(x, a, b)
- if x < a then
- return a
- end
- if x > b then
- return b
- end
- return x
- end
- ----------------------------------------------------------------
- -- SID file parser
- ----------------------------------------------------------------
- local SID = {}
- function SID.readBE16(data, p)
- local a, b = string.byte(data, p, p + 1)
- if not a or not b then
- error("Unexpected end of SID header.")
- end
- return a * 256 + b
- end
- function SID.readBE32(data, p)
- local a, b, c, d = string.byte(data, p, p + 3)
- if not a or not b or not c or not d then
- error("Unexpected end of SID header.")
- end
- return ((a * 256 + b) * 256 + c) * 256 + d
- end
- function SID.text(data, p, length)
- local s = data:sub(p, p + length - 1)
- s = s:gsub("%z.*$", "")
- s = s:gsub("[%c]", "")
- return s
- end
- function SID.decodeClock(flags)
- if flags == 0 then
- return "unknown"
- end
- local clockBits =
- bit.band(
- bit.rshift(flags, 2),
- 0x03
- )
- if clockBits == 1 then
- return "PAL"
- end
- if clockBits == 2 then
- return "NTSC"
- end
- if clockBits == 3 then
- return "PAL+NTSC"
- end
- return "unknown"
- end
- function SID.decodeModel(flags, shift)
- local modelBits =
- bit.band(
- bit.rshift(flags, shift),
- 0x03
- )
- if modelBits == 1 then
- return "6581"
- end
- if modelBits == 2 then
- return "8580"
- end
- if modelBits == 3 then
- return "6581/8580"
- end
- return "unknown"
- end
- ----------------------------------------------------------------
- -- PSID/RSID multi-SID address decoding
- --
- -- Header byte 0x42 = $D420
- -- Header byte 0x50 = $D500
- -- Header byte 0xE0 = $DE00
- --
- -- Only even values in:
- -- $42-$7F
- -- $E0-$FE
- -- are valid.
- ----------------------------------------------------------------
- function SID.decodeSIDAddress(selector)
- if not selector or selector == 0 then
- return 0
- end
- selector = bit.band(selector, 0xFF)
- if bit.band(selector, 1) ~= 0 then
- return 0
- end
- if selector < 0x42 then
- return 0
- end
- if selector > 0x7F and selector < 0xE0 then
- return 0
- end
- if selector > 0xFE then
- return 0
- end
- return 0xD000 + bit.lshift(selector, 4)
- end
- function SID.loadFile(path)
- local f, err = io.open(path, "rb")
- if not f then
- error("Cannot open SID file: " .. tostring(err))
- end
- local data = f:read("*a")
- f:close()
- if not data or #data < 0x76 then
- error("File is too small to be a valid SID file.")
- end
- local magic = data:sub(1, 4)
- if magic ~= "PSID" and magic ~= "RSID" then
- error("Not a PSID/RSID file.")
- end
- local version = SID.readBE16(data, 5)
- local dataOffset = SID.readBE16(data, 7)
- local loadAddress = SID.readBE16(data, 9)
- local initAddress = SID.readBE16(data, 11)
- local playAddress = SID.readBE16(data, 13)
- local songs = SID.readBE16(data, 15)
- local startSong = SID.readBE16(data, 17)
- local speed = SID.readBE32(data, 19)
- if songs < 1 then
- songs = 1
- end
- if startSong < 1 or startSong > songs then
- startSong = 1
- end
- if dataOffset < 0x76 or dataOffset > #data then
- error("Invalid SID data offset.")
- end
- local flags = 0
- if version >= 2 and #data >= 0x7C then
- flags = SID.readBE16(data, 0x77)
- end
- local secondSID = 0
- local thirdSID = 0
- if version >= 3 and #data >= 0x7C then
- secondSID =
- SID.decodeSIDAddress(
- string.byte(data, 0x7B)
- )
- end
- if version >= 4 and #data >= 0x7C then
- thirdSID =
- SID.decodeSIDAddress(
- string.byte(data, 0x7C)
- )
- end
- -- A secondary SID cannot be the primary SID.
- if secondSID == SID_BASE then
- secondSID = 0
- end
- -- Third SID must not collide with SID #1 or SID #2.
- if thirdSID == SID_BASE or
- thirdSID == secondSID then
- thirdSID = 0
- end
- local name = SID.text(data, 0x15, 32)
- local author = SID.text(data, 0x35, 32)
- local released = SID.text(data, 0x55, 32)
- local binary = data:sub(dataOffset + 1)
- if loadAddress == 0 then
- if #binary < 2 then
- error("SID data has no embedded load address.")
- end
- local a = string.byte(binary, 1)
- local b = string.byte(binary, 2)
- loadAddress = a + b * 256
- binary = binary:sub(3)
- end
- if loadAddress < 0 or
- loadAddress + #binary > RAM_SIZE then
- error("SID program does not fit into C64 memory.")
- end
- local effectiveInitAddress = initAddress
- if effectiveInitAddress == 0 then
- effectiveInitAddress = loadAddress
- end
- local sidBases = {
- SID_BASE
- }
- if secondSID ~= 0 then
- sidBases[2] = secondSID
- end
- return {
- magic = magic,
- version = version,
- dataOffset = dataOffset,
- loadAddress = loadAddress,
- initAddress = initAddress,
- effectiveInitAddress = effectiveInitAddress,
- playAddress = playAddress,
- songs = songs,
- startSong = startSong,
- speed = speed,
- flags = flags,
- clock = SID.decodeClock(flags),
- sidModel1 = SID.decodeModel(flags, 4),
- sidModel2 = SID.decodeModel(flags, 6),
- sidModel3 = SID.decodeModel(flags, 8),
- secondSID = secondSID,
- thirdSID = thirdSID,
- sidBases = sidBases,
- name = name,
- author = author,
- released = released,
- data = binary
- }
- end
- ----------------------------------------------------------------
- -- SID chip
- ----------------------------------------------------------------
- local SIDChip = {}
- SIDChip.__index = SIDChip
- function SIDChip.new()
- local self = setmetatable({}, SIDChip)
- self.reg = {}
- for i = 0, 0x1F do
- self.reg[i] = 0
- end
- self.voices = {
- {},
- {},
- {}
- }
- return self
- end
- function SIDChip:read(reg)
- reg = bit.band(reg, 0x1F)
- -- These are read-only SID registers. We do not have a
- -- cycle-perfect oscillator/envelope implementation, so
- -- don't pretend that writes to them are valid readback.
- if reg >= 0x19 then
- if reg <= 0x1C then
- return 0
- end
- end
- return self.reg[reg] or 0
- end
- function SIDChip:write(reg, value)
- reg = bit.band(reg, 0x1F)
- value = u8(value)
- -- Pulse width high byte only uses four bits.
- if reg == 0x03 or
- reg == 0x0A or
- reg == 0x11 then
- value = bit.band(value, 0x0F)
- end
- -- Filter control has only the defined low bits plus
- -- voice-3-off in bit 7.
- if reg == 0x18 then
- value =
- bit.band(
- value,
- 0x8F
- )
- end
- -- Oscillator/envelope read-only registers should not
- -- become writable shadow registers.
- if reg >= 0x19 and reg <= 0x1C then
- return
- end
- self.reg[reg] = value
- end
- function SIDChip:masterVolume()
- return bit.band(
- self.reg[0x18] or 0,
- 0x0F
- )
- end
- function SIDChip:voice3Off()
- return bit.band(
- self.reg[0x18] or 0,
- 0x80
- ) ~= 0
- end
- function SIDChip:voice(v)
- local base = (v - 1) * 7
- local out = self.voices[v]
- local freqLo =
- self.reg[base] or 0
- local freqHi =
- self.reg[base + 1] or 0
- local pwLo =
- self.reg[base + 2] or 0
- local pwHi =
- self.reg[base + 3] or 0
- local control =
- self.reg[base + 4] or 0
- local attackDecay =
- self.reg[base + 5] or 0
- local sustainRelease =
- self.reg[base + 6] or 0
- local freq =
- freqLo +
- bit.lshift(freqHi, 8)
- local pulse =
- pwLo +
- bit.lshift(
- bit.band(pwHi, 0x0F),
- 8
- )
- local attack =
- bit.rshift(
- attackDecay,
- 4
- )
- local decay =
- bit.band(
- attackDecay,
- 0x0F
- )
- local sustain =
- bit.rshift(
- sustainRelease,
- 4
- )
- local release =
- bit.band(
- sustainRelease,
- 0x0F
- )
- out.frequency = freq
- out.pulse = pulse
- out.control = control
- out.attack = attack
- out.decay = decay
- out.sustain = sustain
- out.release = release
- out.gate =
- bit.band(control, 0x01) ~= 0
- out.sync =
- bit.band(control, 0x02) ~= 0
- out.ring =
- bit.band(control, 0x04) ~= 0
- out.test =
- bit.band(control, 0x08) ~= 0
- out.triangle =
- bit.band(control, 0x10) ~= 0
- out.saw =
- bit.band(control, 0x20) ~= 0
- out.square =
- bit.band(control, 0x40) ~= 0
- out.noise =
- bit.band(control, 0x80) ~= 0
- return out
- end
- ----------------------------------------------------------------
- -- 6502 CPU
- ----------------------------------------------------------------
- local CPU = {}
- CPU.__index = CPU
- function CPU.new(memory, sids, sidBases)
- local self = setmetatable({}, CPU)
- self.mem = memory
- self.sids = sids
- self.sidBases = sidBases
- self.sidCount = #sids
- self.A = 0
- self.X = 0
- self.Y = 0
- self.P = 0x24
- self.SP = 0xFD
- self.PC = 0
- self.running = true
- return self
- end
- function CPU:sidAt(addr)
- if addr >= SID_BASE and
- addr <= SID_END then
- return self.sids[1],
- addr - SID_BASE
- end
- for i = 2, self.sidCount do
- local base = self.sidBases[i]
- if base and base ~= 0 and
- addr >= base and
- addr <= base + 0x1F then
- return self.sids[i],
- addr - base
- end
- end
- return nil
- end
- function CPU:read(addr)
- addr = bit.band(addr, 0xFFFF)
- local sid, reg =
- self:sidAt(addr)
- if sid then
- return sid:read(reg)
- end
- return self.mem[addr] or 0
- end
- function CPU:write(addr, value)
- addr = bit.band(addr, 0xFFFF)
- value = u8(value)
- local sid, reg =
- self:sidAt(addr)
- if sid then
- sid:write(reg, value)
- return
- end
- self.mem[addr] = value
- end
- function CPU:fetch()
- local pc = self.PC
- local value =
- self:read(pc)
- self.PC =
- bit.band(
- pc + 1,
- 0xFFFF
- )
- return value
- end
- function CPU:fetch16()
- local l = self:fetch()
- local h = self:fetch()
- return l +
- bit.lshift(h, 8)
- end
- function CPU:push(v)
- self:write(
- 0x100 + self.SP,
- v
- )
- self.SP =
- u8(self.SP - 1)
- end
- function CPU:pull()
- self.SP =
- u8(self.SP + 1)
- return self:read(
- 0x100 + self.SP
- )
- end
- function CPU:push16(v)
- self:push(hi(v))
- self:push(lo(v))
- end
- function CPU:pull16()
- local l = self:pull()
- local h = self:pull()
- return l +
- bit.lshift(h, 8)
- end
- function CPU:getFlag(mask)
- return bit.band(
- self.P,
- mask
- ) ~= 0
- end
- function CPU:setFlag(mask, value)
- if value then
- self.P =
- bit.bor(
- self.P,
- mask
- )
- else
- self.P =
- bit.band(
- self.P,
- bit.bnot(mask)
- )
- end
- -- Bit 5 is always high on the 6502 status byte.
- self.P =
- bit.bor(
- self.P,
- 0x20
- )
- end
- function CPU:setZN(v)
- v = u8(v)
- self:setFlag(
- 0x02,
- v == 0
- )
- self:setFlag(
- 0x80,
- bit.band(v, 0x80) ~= 0
- )
- end
- function CPU:adc(v)
- v = u8(v)
- local carry =
- self:getFlag(0x01)
- and 1 or 0
- local a = self.A
- local result =
- a + v + carry
- self:setFlag(
- 0x01,
- result > 255
- )
- local r = u8(result)
- local overflow =
- bit.band(
- bit.bnot(
- bit.bxor(a, v)
- ),
- bit.bxor(a, r),
- 0x80
- ) ~= 0
- self:setFlag(
- 0x40,
- overflow
- )
- self.A = r
- self:setZN(self.A)
- end
- function CPU:sbc(v)
- self:adc(
- bit.bxor(
- v,
- 0xFF
- )
- )
- end
- function CPU:cmp(reg, v)
- v = u8(v)
- local result =
- u8(reg - v)
- self:setFlag(
- 0x01,
- reg >= v
- )
- self:setZN(result)
- end
- function CPU:asl(v)
- v = u8(v)
- local carry =
- bit.band(
- v,
- 0x80
- ) ~= 0
- v =
- u8(
- bit.lshift(v, 1)
- )
- self:setFlag(
- 0x01,
- carry
- )
- self:setZN(v)
- return v
- end
- function CPU:lsr(v)
- v = u8(v)
- local carry =
- bit.band(v, 1) ~= 0
- v =
- bit.rshift(v, 1)
- self:setFlag(
- 0x01,
- carry
- )
- self:setZN(v)
- return v
- end
- function CPU:rol(v)
- v = u8(v)
- local carryIn =
- self:getFlag(0x01)
- and 1 or 0
- local carryOut =
- bit.band(v, 0x80) ~= 0
- v =
- u8(
- bit.lshift(v, 1) +
- carryIn
- )
- self:setFlag(
- 0x01,
- carryOut
- )
- self:setZN(v)
- return v
- end
- function CPU:ror(v)
- v = u8(v)
- local carryIn =
- self:getFlag(0x01)
- and 0x80 or 0
- local carryOut =
- bit.band(v, 1) ~= 0
- v =
- bit.bor(
- bit.rshift(v, 1),
- carryIn
- )
- self:setFlag(
- 0x01,
- carryOut
- )
- self:setZN(v)
- return v
- end
- ----------------------------------------------------------------
- -- Addressing
- ----------------------------------------------------------------
- function CPU:addr(mode)
- if mode == "imp" or
- mode == "acc" then
- return nil
- end
- if mode == "imm" then
- local a = self.PC
- self.PC =
- u16(self.PC + 1)
- return a
- end
- if mode == "zp" then
- return self:fetch()
- end
- if mode == "zpx" then
- return bit.band(
- self:fetch() + self.X,
- 0xFF
- )
- end
- if mode == "zpy" then
- return bit.band(
- self:fetch() + self.Y,
- 0xFF
- )
- end
- if mode == "abs" then
- return self:fetch16()
- end
- if mode == "abx" then
- return u16(
- self:fetch16() +
- self.X
- )
- end
- if mode == "aby" then
- return u16(
- self:fetch16() +
- self.Y
- )
- end
- if mode == "izx" then
- local p =
- bit.band(
- self:fetch() +
- self.X,
- 0xFF
- )
- local l =
- self:read(p)
- local h =
- self:read(
- bit.band(
- p + 1,
- 0xFF
- )
- )
- return l +
- bit.lshift(h, 8)
- end
- if mode == "izy" then
- local p =
- self:fetch()
- local l =
- self:read(p)
- local h =
- self:read(
- bit.band(
- p + 1,
- 0xFF
- )
- )
- return u16(
- l +
- bit.lshift(h, 8) +
- self.Y
- )
- end
- if mode == "ind" then
- local p =
- self:fetch16()
- local l =
- self:read(p)
- -- Correct NMOS 6502 JMP indirect page-wrap behavior.
- local hp =
- bit.bor(
- bit.band(p, 0xFF00),
- bit.band(
- p + 1,
- 0xFF
- )
- )
- local h =
- self:read(hp)
- return l +
- bit.lshift(h, 8)
- end
- if mode == "rel" then
- local r =
- signed8(
- self:fetch()
- )
- return u16(
- self.PC + r
- )
- end
- error(
- "Unknown addressing mode: " ..
- tostring(mode)
- )
- end
- ----------------------------------------------------------------
- -- Opcode table
- ----------------------------------------------------------------
- local opcodeRows = {
- "BRK/imp ORA/izx */imp */imp NOP/zp ORA/zp ASL/zp */imp PHP/imp ORA/imm ASL/acc */imp NOP/abs ORA/abs ASL/abs */imp",
- "BPL/rel ORA/izy */imp */imp NOP/zpx ORA/zpx ASL/zpx */imp CLC/imp ORA/aby NOP/imp */imp NOP/abx ORA/abx ASL/abx */imp",
- "JSR/abs AND/izx */imp */imp BIT/zp AND/zp ROL/zp */imp PLP/imp AND/imm ROL/acc */imp BIT/abs AND/abs ROL/abs */imp",
- "BMI/rel AND/izy */imp */imp NOP/zpx AND/zpx ROL/zpx */imp SEC/imp AND/aby NOP/imp */imp NOP/abx AND/abx ROL/abx */imp",
- "RTI/imp EOR/izx */imp */imp NOP/zp EOR/zp LSR/zp */imp PHA/imp EOR/imm LSR/acc */imp JMP/abs EOR/abs LSR/abs */imp",
- "BVC/rel EOR/izy */imp */imp NOP/zpx EOR/zpx LSR/zpx */imp CLI/imp EOR/aby NOP/imp */imp NOP/abx EOR/abx LSR/abx */imp",
- "RTS/imp ADC/izx */imp */imp NOP/zp ADC/zp ROR/zp */imp PLA/imp ADC/imm ROR/acc */imp JMP/ind ADC/abs ROR/abs */imp",
- "BVS/rel ADC/izy */imp */imp NOP/zpx ADC/zpx ROR/zpx */imp SEI/imp ADC/aby NOP/imp */imp NOP/abx ADC/abx ROR/abx */imp",
- "NOP/imm STA/izx NOP/imm */imp STY/zp STA/zp STX/zp */imp DEY/imp NOP/imm TXA/imp */imp STY/abs STA/abs STX/abs */imp",
- "BCC/rel STA/izy */imp */imp STY/zpx STA/zpx STX/zpy */imp TYA/imp STA/aby TXS/imp */imp */imp STA/abx */imp */imp",
- "LDY/imm LDA/izx LDX/imm */imp LDY/zp LDA/zp LDX/zp */imp TAY/imp LDA/imm TAX/imp */imp LDY/abs LDA/abs LDX/abs */imp",
- "BCS/rel LDA/izy */imp */imp LDY/zpx LDA/zpx LDX/zpy */imp CLV/imp LDA/aby TSX/imp */imp LDY/abx LDA/abx LDX/aby */imp",
- "CPY/imm CMP/izx NOP/imm */imp CPY/zp CMP/zp DEC/zp */imp INY/imp CMP/imm DEX/imp */imp CPY/abs CMP/abs DEC/abs */imp",
- "BNE/rel CMP/izy */imp */imp NOP/zpx CMP/zpx DEC/zpx */imp CLD/imp CMP/aby NOP/imp */imp NOP/abx CMP/abx DEC/abx */imp",
- "CPX/imm SBC/izx NOP/imm */imp CPX/zp SBC/zp INC/zp */imp INX/imp SBC/imm NOP/imp */imp CPX/abs SBC/abs INC/abs */imp",
- "BEQ/rel SBC/izy */imp */imp NOP/zpx SBC/zpx INC/zpx */imp SED/imp SBC/aby NOP/imp */imp NOP/abx SBC/abx INC/abx */imp"
- }
- local OPCODE = {}
- for rowIndex = 1, #opcodeRows do
- local col = 0
- for token in string.gmatch(
- opcodeRows[rowIndex],
- "%S+"
- ) do
- col = col + 1
- local slash =
- string.find(
- token,
- "/",
- 1,
- true
- )
- local mnemonic =
- token:sub(
- 1,
- slash - 1
- )
- local mode =
- token:sub(
- slash + 1
- )
- local opcode =
- (rowIndex - 1) * 16 +
- (col - 1)
- OPCODE[opcode] = {
- op = mnemonic,
- mode = mode
- }
- end
- end
- ----------------------------------------------------------------
- -- Illegal / undocumented opcodes
- ----------------------------------------------------------------
- local ILLEGAL = {
- [0x03] = {"SLO", "izx"},
- [0x07] = {"SLO", "zp"},
- [0x0F] = {"SLO", "abs"},
- [0x13] = {"SLO", "izy"},
- [0x17] = {"SLO", "zpx"},
- [0x1B] = {"SLO", "aby"},
- [0x1F] = {"SLO", "abx"},
- [0x23] = {"RLA", "izx"},
- [0x27] = {"RLA", "zp"},
- [0x2F] = {"RLA", "abs"},
- [0x33] = {"RLA", "izy"},
- [0x37] = {"RLA", "zpx"},
- [0x3B] = {"RLA", "aby"},
- [0x3F] = {"RLA", "abx"},
- [0x43] = {"SRE", "izx"},
- [0x47] = {"SRE", "zp"},
- [0x4F] = {"SRE", "abs"},
- [0x53] = {"SRE", "izy"},
- [0x57] = {"SRE", "zpx"},
- [0x5B] = {"SRE", "aby"},
- [0x5F] = {"SRE", "abx"},
- [0x63] = {"RRA", "izx"},
- [0x67] = {"RRA", "zp"},
- [0x6F] = {"RRA", "abs"},
- [0x73] = {"RRA", "izy"},
- [0x77] = {"RRA", "zpx"},
- [0x7B] = {"RRA", "aby"},
- [0x7F] = {"RRA", "abx"},
- [0x83] = {"SAX", "izx"},
- [0x87] = {"SAX", "zp"},
- [0x8F] = {"SAX", "abs"},
- [0x97] = {"SAX", "zpy"},
- [0xA3] = {"LAX", "izx"},
- [0xA7] = {"LAX", "zp"},
- [0xAB] = {"LAX", "imm"},
- [0xAF] = {"LAX", "abs"},
- [0xB3] = {"LAX", "izy"},
- [0xB7] = {"LAX", "zpy"},
- [0xBF] = {"LAX", "aby"},
- [0xC3] = {"DCP", "izx"},
- [0xC7] = {"DCP", "zp"},
- [0xCF] = {"DCP", "abs"},
- [0xD3] = {"DCP", "izy"},
- [0xD7] = {"DCP", "zpx"},
- [0xDB] = {"DCP", "aby"},
- [0xDF] = {"DCP", "abx"},
- [0xE3] = {"ISC", "izx"},
- [0xE7] = {"ISC", "zp"},
- [0xEF] = {"ISC", "abs"},
- [0xF3] = {"ISC", "izy"},
- [0xF7] = {"ISC", "zpx"},
- [0xFB] = {"ISC", "aby"},
- [0xFF] = {"ISC", "abx"},
- [0x1A] = {"NOP", "imp"},
- [0x3A] = {"NOP", "imp"},
- [0x5A] = {"NOP", "imp"},
- [0x7A] = {"NOP", "imp"},
- [0xDA] = {"NOP", "imp"},
- [0xFA] = {"NOP", "imp"},
- [0x80] = {"NOP", "imm"},
- [0x82] = {"NOP", "imm"},
- [0x89] = {"NOP", "imm"},
- [0x04] = {"NOP", "zp"},
- [0x44] = {"NOP", "zp"},
- [0x64] = {"NOP", "zp"},
- [0x14] = {"NOP", "zpx"},
- [0x34] = {"NOP", "zpx"},
- [0x54] = {"NOP", "zpx"},
- [0x74] = {"NOP", "zpx"},
- [0xD4] = {"NOP", "zpx"},
- [0xF4] = {"NOP", "zpx"},
- [0x0C] = {"NOP", "abs"},
- [0x1C] = {"NOP", "abx"},
- [0x3C] = {"NOP", "abx"},
- [0x5C] = {"NOP", "abx"},
- [0x7C] = {"NOP", "abx"},
- [0xDC] = {"NOP", "abx"},
- [0xFC] = {"NOP", "abx"},
- [0x0B] = {"ANC", "imm"},
- [0x2B] = {"ANC", "imm"},
- [0x4B] = {"ALR", "imm"},
- [0x6B] = {"ARR", "imm"},
- [0xCB] = {"AXS", "imm"},
- [0xBB] = {"LAS", "aby"},
- [0x9C] = {"SHY", "abx"},
- [0x9E] = {"SHX", "aby"},
- [0x9B] = {"TAS", "aby"},
- [0x93] = {"AHX", "izy"},
- [0x9F] = {"AHX", "aby"}
- }
- for opcode, info in pairs(ILLEGAL) do
- OPCODE[opcode] = {
- op = info[1],
- mode = info[2]
- }
- end
- local KIL = {
- [0x02] = true,
- [0x12] = true,
- [0x22] = true,
- [0x32] = true,
- [0x42] = true,
- [0x52] = true,
- [0x62] = true,
- [0x72] = true,
- [0x92] = true,
- [0xB2] = true,
- [0xD2] = true,
- [0xF2] = true
- }
- for opcode in pairs(KIL) do
- OPCODE[opcode] = {
- op = "KIL",
- mode = "imp"
- }
- end
- ----------------------------------------------------------------
- -- CPU execution
- ----------------------------------------------------------------
- function CPU:execute(opcode)
- local entry = OPCODE[opcode]
- if not entry then
- return
- end
- local op = entry.op
- local mode = entry.mode
- if op == "KIL" then
- self.running = false
- return
- end
- ------------------------------------------------------------
- -- Undocumented one/two/three byte NOPs
- ------------------------------------------------------------
- if op == "*" then
- if mode == "imm" or
- mode == "zp" or
- mode == "zpx" or
- mode == "zpy" or
- mode == "rel" then
- self.PC =
- u16(self.PC + 1)
- elseif mode == "abs" or
- mode == "abx" or
- mode == "aby" then
- self.PC =
- u16(self.PC + 2)
- elseif mode == "izx" or
- mode == "izy" or
- mode == "ind" then
- self.PC =
- u16(self.PC + 1)
- end
- return
- end
- ------------------------------------------------------------
- -- Branches
- ------------------------------------------------------------
- if op == "BPL" then
- local a = self:addr("rel")
- if not self:getFlag(0x80) then
- self.PC = a
- end
- return
- end
- if op == "BMI" then
- local a = self:addr("rel")
- if self:getFlag(0x80) then
- self.PC = a
- end
- return
- end
- if op == "BVC" then
- local a = self:addr("rel")
- if not self:getFlag(0x40) then
- self.PC = a
- end
- return
- end
- if op == "BVS" then
- local a = self:addr("rel")
- if self:getFlag(0x40) then
- self.PC = a
- end
- return
- end
- if op == "BCC" then
- local a = self:addr("rel")
- if not self:getFlag(0x01) then
- self.PC = a
- end
- return
- end
- if op == "BCS" then
- local a = self:addr("rel")
- if self:getFlag(0x01) then
- self.PC = a
- end
- return
- end
- if op == "BNE" then
- local a = self:addr("rel")
- if not self:getFlag(0x02) then
- self.PC = a
- end
- return
- end
- if op == "BEQ" then
- local a = self:addr("rel")
- if self:getFlag(0x02) then
- self.PC = a
- end
- return
- end
- ------------------------------------------------------------
- -- Jumps / subroutines / interrupts
- ------------------------------------------------------------
- if op == "JMP" then
- self.PC = self:addr(mode)
- return
- end
- if op == "JSR" then
- local target =
- self:addr("abs")
- self:push16(
- u16(self.PC - 1)
- )
- self.PC = target
- return
- end
- if op == "RTS" then
- self.PC =
- u16(
- self:pull16() + 1
- )
- return
- end
- if op == "RTI" then
- self.P =
- self:pull()
- self.PC =
- self:pull16()
- self.P =
- bit.bor(
- self.P,
- 0x20
- )
- return
- end
- if op == "BRK" then
- self.PC =
- u16(self.PC + 1)
- self:push16(self.PC)
- self:push(
- bit.bor(
- self.P,
- 0x30
- )
- )
- self:setFlag(
- 0x04,
- true
- )
- local l =
- self:read(0xFFFE)
- local h =
- self:read(0xFFFF)
- self.PC =
- l +
- bit.lshift(h, 8)
- return
- end
- ------------------------------------------------------------
- -- Flags
- ------------------------------------------------------------
- if op == "CLC" then
- self:setFlag(0x01, false)
- return
- end
- if op == "SEC" then
- self:setFlag(0x01, true)
- return
- end
- if op == "CLI" then
- self:setFlag(0x04, false)
- return
- end
- if op == "SEI" then
- self:setFlag(0x04, true)
- return
- end
- if op == "CLV" then
- self:setFlag(0x40, false)
- return
- end
- if op == "CLD" then
- self:setFlag(0x08, false)
- return
- end
- if op == "SED" then
- self:setFlag(0x08, true)
- return
- end
- ------------------------------------------------------------
- -- Stack
- ------------------------------------------------------------
- if op == "PHA" then
- self:push(self.A)
- return
- end
- if op == "PLA" then
- self.A = self:pull()
- self:setZN(self.A)
- return
- end
- if op == "PHP" then
- self:push(
- bit.bor(
- self.P,
- 0x30
- )
- )
- return
- end
- if op == "PLP" then
- self.P = self:pull()
- self.P =
- bit.bor(
- self.P,
- 0x20
- )
- return
- end
- ------------------------------------------------------------
- -- Transfers
- ------------------------------------------------------------
- if op == "TAX" then
- self.X = self.A
- self:setZN(self.X)
- return
- end
- if op == "TAY" then
- self.Y = self.A
- self:setZN(self.Y)
- return
- end
- if op == "TXA" then
- self.A = self.X
- self:setZN(self.A)
- return
- end
- if op == "TYA" then
- self.A = self.Y
- self:setZN(self.A)
- return
- end
- if op == "TSX" then
- self.X = self.SP
- self:setZN(self.X)
- return
- end
- if op == "TXS" then
- self.SP = self.X
- return
- end
- ------------------------------------------------------------
- -- Inc / dec registers
- ------------------------------------------------------------
- if op == "INX" then
- self.X = u8(self.X + 1)
- self:setZN(self.X)
- return
- end
- if op == "DEX" then
- self.X = u8(self.X - 1)
- self:setZN(self.X)
- return
- end
- if op == "INY" then
- self.Y = u8(self.Y + 1)
- self:setZN(self.Y)
- return
- end
- if op == "DEY" then
- self.Y = u8(self.Y - 1)
- self:setZN(self.Y)
- return
- end
- ------------------------------------------------------------
- -- Shifts
- ------------------------------------------------------------
- if op == "ASL" then
- if mode == "acc" then
- self.A =
- self:asl(self.A)
- else
- local a =
- self:addr(mode)
- self:write(
- a,
- self:asl(
- self:read(a)
- )
- )
- end
- return
- end
- if op == "LSR" then
- if mode == "acc" then
- self.A =
- self:lsr(self.A)
- else
- local a =
- self:addr(mode)
- self:write(
- a,
- self:lsr(
- self:read(a)
- )
- )
- end
- return
- end
- if op == "ROL" then
- if mode == "acc" then
- self.A =
- self:rol(self.A)
- else
- local a =
- self:addr(mode)
- self:write(
- a,
- self:rol(
- self:read(a)
- )
- )
- end
- return
- end
- if op == "ROR" then
- if mode == "acc" then
- self.A =
- self:ror(self.A)
- else
- local a =
- self:addr(mode)
- self:write(
- a,
- self:ror(
- self:read(a)
- )
- )
- end
- return
- end
- ------------------------------------------------------------
- -- Loads / stores
- ------------------------------------------------------------
- if op == "LDA" then
- local a =
- self:addr(mode)
- self.A =
- self:read(a)
- self:setZN(self.A)
- return
- end
- if op == "LDX" then
- local a =
- self:addr(mode)
- self.X =
- self:read(a)
- self:setZN(self.X)
- return
- end
- if op == "LDY" then
- local a =
- self:addr(mode)
- self.Y =
- self:read(a)
- self:setZN(self.Y)
- return
- end
- if op == "STA" then
- local a =
- self:addr(mode)
- self:write(
- a,
- self.A
- )
- return
- end
- if op == "STX" then
- local a =
- self:addr(mode)
- self:write(
- a,
- self.X
- )
- return
- end
- if op == "STY" then
- local a =
- self:addr(mode)
- self:write(
- a,
- self.Y
- )
- return
- end
- ------------------------------------------------------------
- -- Logic
- ------------------------------------------------------------
- if op == "AND" then
- local a =
- self:addr(mode)
- self.A =
- bit.band(
- self.A,
- self:read(a)
- )
- self:setZN(self.A)
- return
- end
- if op == "ORA" then
- local a =
- self:addr(mode)
- self.A =
- bit.bor(
- self.A,
- self:read(a)
- )
- self:setZN(self.A)
- return
- end
- if op == "EOR" then
- local a =
- self:addr(mode)
- self.A =
- bit.bxor(
- self.A,
- self:read(a)
- )
- self:setZN(self.A)
- return
- end
- if op == "BIT" then
- local a =
- self:addr(mode)
- local v =
- self:read(a)
- self:setFlag(
- 0x02,
- bit.band(
- self.A,
- v
- ) == 0
- )
- self:setFlag(
- 0x80,
- bit.band(v, 0x80) ~= 0
- )
- self:setFlag(
- 0x40,
- bit.band(v, 0x40) ~= 0
- )
- return
- end
- ------------------------------------------------------------
- -- Arithmetic
- ------------------------------------------------------------
- if op == "ADC" then
- local a =
- self:addr(mode)
- self:adc(
- self:read(a)
- )
- return
- end
- if op == "SBC" then
- local a =
- self:addr(mode)
- self:sbc(
- self:read(a)
- )
- return
- end
- if op == "CMP" then
- local a =
- self:addr(mode)
- self:cmp(
- self.A,
- self:read(a)
- )
- return
- end
- if op == "CPX" then
- local a =
- self:addr(mode)
- self:cmp(
- self.X,
- self:read(a)
- )
- return
- end
- if op == "CPY" then
- local a =
- self:addr(mode)
- self:cmp(
- self.Y,
- self:read(a)
- )
- return
- end
- ------------------------------------------------------------
- -- Memory inc/dec
- ------------------------------------------------------------
- if op == "INC" then
- local a =
- self:addr(mode)
- local v =
- u8(
- self:read(a) + 1
- )
- self:write(a, v)
- self:setZN(v)
- return
- end
- if op == "DEC" then
- local a =
- self:addr(mode)
- local v =
- u8(
- self:read(a) - 1
- )
- self:write(a, v)
- self:setZN(v)
- return
- end
- ------------------------------------------------------------
- -- NOP
- ------------------------------------------------------------
- if op == "NOP" then
- if mode ~= "imp" and
- mode ~= "acc" then
- self:addr(mode)
- end
- return
- end
- ------------------------------------------------------------
- -- Undocumented opcodes
- ------------------------------------------------------------
- if op == "SLO" then
- local a =
- self:addr(mode)
- local v =
- self:asl(
- self:read(a)
- )
- self:write(a, v)
- self.A =
- bit.bor(
- self.A,
- v
- )
- self:setZN(self.A)
- return
- end
- if op == "RLA" then
- local a =
- self:addr(mode)
- local v =
- self:rol(
- self:read(a)
- )
- self:write(a, v)
- self.A =
- bit.band(
- self.A,
- v
- )
- self:setZN(self.A)
- return
- end
- if op == "SRE" then
- local a =
- self:addr(mode)
- local v =
- self:lsr(
- self:read(a)
- )
- self:write(a, v)
- self.A =
- bit.bxor(
- self.A,
- v
- )
- self:setZN(self.A)
- return
- end
- if op == "RRA" then
- local a =
- self:addr(mode)
- local v =
- self:ror(
- self:read(a)
- )
- self:write(a, v)
- self:adc(v)
- return
- end
- if op == "SAX" then
- local a =
- self:addr(mode)
- self:write(
- a,
- bit.band(
- self.A,
- self.X
- )
- )
- return
- end
- if op == "LAX" then
- local a =
- self:addr(mode)
- local v =
- self:read(a)
- self.A = v
- self.X = v
- self:setZN(v)
- return
- end
- if op == "DCP" then
- local a =
- self:addr(mode)
- local v =
- u8(
- self:read(a) - 1
- )
- self:write(a, v)
- self:cmp(
- self.A,
- v
- )
- return
- end
- if op == "ISC" then
- local a =
- self:addr(mode)
- local v =
- u8(
- self:read(a) + 1
- )
- self:write(a, v)
- self:sbc(v)
- return
- end
- if op == "ANC" then
- local a =
- self:addr(mode)
- self.A =
- bit.band(
- self.A,
- self:read(a)
- )
- self:setZN(self.A)
- self:setFlag(
- 0x01,
- bit.band(
- self.A,
- 0x80
- ) ~= 0
- )
- return
- end
- if op == "ALR" then
- local a =
- self:addr(mode)
- self.A =
- bit.band(
- self.A,
- self:read(a)
- )
- self.A =
- self:lsr(self.A)
- return
- end
- if op == "ARR" then
- local a =
- self:addr(mode)
- local value =
- bit.band(
- self.A,
- self:read(a)
- )
- local carryIn =
- self:getFlag(0x01)
- and 0x80
- or 0
- value =
- bit.bor(
- bit.rshift(value, 1),
- carryIn
- )
- self.A = value
- self:setZN(value)
- self:setFlag(
- 0x01,
- bit.band(
- value,
- 0x40
- ) ~= 0
- )
- self:setFlag(
- 0x40,
- bit.bxor(
- bit.band(
- value,
- 0x40
- ),
- bit.band(
- value,
- 0x20
- )
- ) ~= 0
- )
- return
- end
- if op == "AXS" then
- local a =
- self:addr(mode)
- local value =
- self:read(a)
- local result =
- bit.band(
- self.A,
- self.X
- ) - value
- self:setFlag(
- 0x01,
- result >= 0
- )
- self.X =
- u8(result)
- self:setZN(self.X)
- return
- end
- if op == "LAS" then
- local a =
- self:addr(mode)
- local v =
- bit.band(
- self:read(a),
- self.SP
- )
- self.A = v
- self.X = v
- self.SP = v
- self:setZN(v)
- return
- end
- if op == "SHY" then
- local base =
- self:fetch16()
- local address =
- u16(
- base + self.X
- )
- local value =
- bit.band(
- self.Y,
- hi(address) + 1
- )
- self:write(
- address,
- value
- )
- return
- end
- if op == "SHX" then
- local base =
- self:fetch16()
- local address =
- u16(
- base + self.Y
- )
- local value =
- bit.band(
- self.X,
- hi(address) + 1
- )
- self:write(
- address,
- value
- )
- return
- end
- if op == "TAS" then
- local base =
- self:fetch16()
- local address =
- u16(
- base + self.Y
- )
- self.SP =
- bit.band(
- self.A,
- self.X
- )
- local value =
- bit.band(
- self.SP,
- hi(address) + 1
- )
- self:write(
- address,
- value
- )
- return
- end
- if op == "AHX" then
- local a =
- self:addr(mode)
- local value =
- bit.band(
- self.A,
- self.X,
- hi(a) + 1
- )
- self:write(
- a,
- value
- )
- return
- end
- end
- ----------------------------------------------------------------
- -- CPU execution loops
- ----------------------------------------------------------------
- function CPU:runUntilReturn(maxInstructions)
- maxInstructions =
- maxInstructions or
- MAX_INSTRUCTIONS_PER_CALL
- local count = 0
- while self.running and
- count < maxInstructions do
- if self.PC == 0xFFFF then
- self.running = false
- return true
- end
- local opcode =
- self:fetch()
- self:execute(opcode)
- count = count + 1
- end
- if not self.running then
- return false,
- "CPU stopped on KIL/JAM opcode"
- end
- self.running = false
- return false,
- "CPU watchdog triggered"
- end
- ----------------------------------------------------------------
- -- IRQ handling
- ----------------------------------------------------------------
- function CPU:finishSyntheticIRQ()
- -- Equivalent to the RTI at the end of a normal IRQ,
- -- restoring the synthetic interrupt frame.
- self.P =
- self:pull()
- self.PC =
- self:pull16()
- self.P =
- bit.bor(
- self.P,
- 0x20
- )
- self.PC =
- u16(self.PC)
- return true
- end
- function CPU:runUntilIRQReturn(maxInstructions)
- maxInstructions =
- maxInstructions or
- MAX_INSTRUCTIONS_PER_IRQ
- local count = 0
- while self.running and
- count < maxInstructions do
- -- Standard PSID IRQ wrappers commonly JMP to the
- -- KERNAL IRQ exit at $EA31.
- if self.PC == KERNAL_IRQ_EXIT then
- return self:finishSyntheticIRQ()
- end
- local opcode =
- self:fetch()
- if opcode == 0x40 then
- -- RTI.
- self:execute(opcode)
- return true
- end
- self:execute(opcode)
- count = count + 1
- end
- if not self.running then
- return false,
- "IRQ CPU stopped on KIL/JAM opcode"
- end
- return false,
- "IRQ watchdog triggered"
- end
- function CPU:interrupt(vector)
- -- Hardware IRQ entry:
- --
- -- push PC
- -- push P with B cleared
- -- set I
- -- load vector
- --
- -- We don't emulate individual CPU cycles, but the register
- -- and stack behavior is preserved closely enough for PSID
- -- IRQ-driven players.
- local oldP = self.P
- self:push16(self.PC)
- self:push(
- bit.band(
- oldP,
- 0xEF
- )
- )
- self.P =
- bit.bor(
- bit.band(
- oldP,
- 0xFB
- ),
- 0x20
- )
- local low =
- self:read(vector)
- local high =
- self:read(
- bit.band(
- vector + 1,
- 0xFFFF
- )
- )
- self.PC =
- low +
- bit.lshift(
- high,
- 8
- )
- self.running = true
- return self:runUntilIRQReturn()
- end
- function CPU:call(address, a, x, y)
- self.A = u8(a or 0)
- self.X = u8(x or 0)
- self.Y = u8(y or 0)
- self.P = 0x24
- self.SP = 0xFB
- self.running = true
- self:push16(0xFFFE)
- self.PC =
- u16(address)
- return self:runUntilReturn()
- end
- ----------------------------------------------------------------
- -- Audio
- ----------------------------------------------------------------
- local AUDIO = {}
- AUDIO.__index = AUDIO
- local ATTACK_TIMES = {
- 2, 8, 16, 24,
- 38, 56, 68, 80,
- 100, 250, 500, 800,
- 1000, 3000, 8000, 24000
- }
- local DECAY_TIMES = {
- 6, 24, 48, 72,
- 114, 168, 204, 240,
- 300, 750, 1500, 2400,
- 3000, 9000, 24000, 72000
- }
- function AUDIO.new(sids, sidCount)
- local self =
- setmetatable({}, AUDIO)
- self.sids = sids
- self.sidCount = sidCount
- self.volume =
- DEFAULT_VOLUME
- self.channels = {}
- for i = 1, TOTAL_VOICES do
- self.channels[i] = {
- opened = false,
- gate = false,
- waveform = nil,
- frequency = nil,
- volume = nil,
- attack = nil,
- decay = nil,
- sustain = nil,
- release = nil,
- audible = false
- }
- end
- return self
- end
- function AUDIO:frequencyFromSID(value, pal)
- value = bit.band(
- value or 0,
- 0xFFFF
- )
- -- SID frequency register zero is genuinely zero.
- -- The previous version converted it to 1 Hz.
- if value == 0 then
- return 0
- end
- local clock =
- pal and
- PAL_CLOCK or
- NTSC_CLOCK
- local f =
- value *
- clock /
- SID_FREQ_DIV
- if f <= 0 then
- return 0
- end
- if f > 20000 then
- return 20000
- end
- return f
- end
- function AUDIO:waveform(v)
- -- Priority follows the SID control bits, with noise
- -- taking precedence in the approximation.
- if v.noise then
- return WAVE_NOISE
- end
- if v.square then
- return WAVE_SQUARE
- end
- if v.saw then
- return WAVE_SAW
- end
- if v.triangle then
- return WAVE_TRIANGLE
- end
- return WAVE_SQUARE
- end
- function AUDIO:adsr(v)
- local attack =
- ATTACK_TIMES[
- v.attack + 1
- ] or 8
- local decay =
- DECAY_TIMES[
- v.decay + 1
- ] or 24
- local sustain =
- v.sustain / 15
- local release =
- DECAY_TIMES[
- v.release + 1
- ] or 24
- return
- attack,
- decay,
- sustain,
- release
- end
- function AUDIO:setVolume(v)
- self.volume =
- clamp(
- v,
- MIN_VOLUME,
- MAX_VOLUME
- )
- end
- function AUDIO:updateVoice(
- channel,
- voiceNumber,
- v,
- pal,
- masterVolume,
- muted
- )
- local ch =
- self.channels[channel]
- local frequency =
- self:frequencyFromSID(
- v.frequency,
- pal
- )
- local wave =
- self:waveform(v)
- local attack,
- decay,
- sustain,
- release =
- self:adsr(v)
- local hasWave =
- v.noise or
- v.square or
- v.saw or
- v.triangle
- local audible =
- (not v.test) and
- hasWave and
- (not muted)
- local master =
- masterVolume / 15
- local targetVolume
- if audible then
- targetVolume =
- self.volume *
- master
- else
- targetVolume = 0
- end
- ------------------------------------------------------------
- -- Waveform
- ------------------------------------------------------------
- if ch.waveform ~= wave then
- sound.setWave(
- channel,
- wave
- )
- ch.waveform = wave
- end
- ------------------------------------------------------------
- -- Frequency
- ------------------------------------------------------------
- if ch.frequency == nil or
- math.abs(
- ch.frequency - frequency
- ) > 0.25 then
- sound.setFrequency(
- channel,
- frequency
- )
- ch.frequency =
- frequency
- end
- ------------------------------------------------------------
- -- ADSR
- ------------------------------------------------------------
- if ch.attack ~= attack or
- ch.decay ~= decay or
- ch.sustain ~= sustain or
- ch.release ~= release then
- sound.setADSR(
- channel,
- attack,
- decay,
- sustain,
- release
- )
- ch.attack = attack
- ch.decay = decay
- ch.sustain = sustain
- ch.release = release
- end
- ------------------------------------------------------------
- -- Volume
- ------------------------------------------------------------
- if ch.volume == nil or
- math.abs(
- ch.volume -
- targetVolume
- ) > 0.001 then
- sound.setVolume(
- channel,
- targetVolume
- )
- ch.volume =
- targetVolume
- end
- ------------------------------------------------------------
- -- Gate
- --
- -- Keep opening based on the SID gate rather than on
- -- "audible", because TEST / voice-3-off / volume-zero
- -- states should not accidentally destroy the ADSR state.
- ------------------------------------------------------------
- if v.gate then
- if not ch.opened then
- sound.open(channel)
- ch.opened = true
- end
- else
- if ch.opened then
- sound.close(channel)
- ch.opened = false
- end
- end
- ch.gate =
- v.gate
- ch.audible =
- audible
- end
- function AUDIO:render(
- pal,
- frameMilliseconds
- )
- for sidIndex = 1, self.sidCount do
- local chip =
- self.sids[sidIndex]
- local masterVolume =
- chip:masterVolume()
- local voice3Off =
- chip:voice3Off()
- local channelBase =
- (sidIndex - 1) *
- VOICES_PER_SID
- for voiceNumber = 1, 3 do
- local channel =
- channelBase +
- voiceNumber
- local v =
- chip:voice(
- voiceNumber
- )
- local muted =
- voiceNumber == 3 and
- voice3Off
- self:updateVoice(
- channel,
- voiceNumber,
- v,
- pal,
- masterVolume,
- muted
- )
- end
- end
- sound.delay(
- frameMilliseconds
- )
- sound.process()
- end
- function AUDIO:resetChannels()
- for i = 1, TOTAL_VOICES do
- local ch =
- self.channels[i]
- ch.opened = false
- ch.gate = false
- ch.waveform = nil
- ch.frequency = nil
- ch.volume = nil
- ch.attack = nil
- ch.decay = nil
- ch.sustain = nil
- ch.release = nil
- ch.audible = false
- end
- end
- function AUDIO:stop()
- local anyOpen = false
- for i = 1, TOTAL_VOICES do
- local ch =
- self.channels[i]
- if ch.opened then
- sound.close(i)
- ch.opened = false
- anyOpen = true
- end
- end
- if anyOpen then
- sound.process()
- end
- end
- ----------------------------------------------------------------
- -- Machine
- ----------------------------------------------------------------
- local Machine = {}
- Machine.__index = Machine
- function Machine.new(tune)
- local self =
- setmetatable({}, Machine)
- -- Sparse RAM table.
- --
- -- Missing addresses read as zero through CPU:read().
- -- This avoids allocating and initializing 65,536 Lua entries
- -- before every tune.
- self.memory = {}
- self.tune = tune
- self.sids = {
- SIDChip.new()
- }
- if tune.secondSID and
- tune.secondSID ~= 0 then
- self.sids[2] =
- SIDChip.new()
- end
- self.sidBases = {
- SID_BASE,
- tune.secondSID or 0
- }
- self.sidCount =
- #self.sids
- self.cpu =
- CPU.new(
- self.memory,
- self.sids,
- self.sidBases
- )
- self.audio =
- AUDIO.new(
- self.sids,
- self.sidCount
- )
- self.song =
- tune.startSong
- if tune.clock == "NTSC" then
- self.pal = false
- else
- self.pal = true
- end
- self.frameRate =
- self.pal and
- PAL_HZ or
- NTSC_HZ
- self.irqMode =
- tune.playAddress == 0
- return self
- end
- function Machine:setClock(pal)
- self.pal = pal
- self.frameRate =
- pal and
- PAL_HZ or
- NTSC_HZ
- self.memory[0x02A6] =
- pal and
- 0x01 or
- 0x00
- end
- function Machine:load()
- local address =
- self.tune.loadAddress
- local data =
- self.tune.data
- local memory =
- self.memory
- for i = 1, #data do
- memory[
- address + i - 1
- ] =
- string.byte(
- data,
- i
- )
- end
- memory[0x02A6] =
- self.pal and
- 0x01 or
- 0x00
- ------------------------------------------------------------
- -- Reset vector.
- --
- -- We do not put playAddress into $FFFE/$FFFF.
- -- Those are the actual BRK/IRQ vector, and PSID playAddress
- -- is not the same thing as that vector.
- ------------------------------------------------------------
- memory[0xFFFA] = 0
- memory[0xFFFB] = 0
- memory[0xFFFC] =
- lo(
- self.tune.effectiveInitAddress
- )
- memory[0xFFFD] =
- hi(
- self.tune.effectiveInitAddress
- )
- -- Leave $FFFE/$FFFF as zero unless the tune itself writes
- -- them. The $0314/$0315 vector is what $0000 play tunes use.
- end
- function Machine:init(song)
- song =
- clamp(
- song or self.song,
- 1,
- self.tune.songs
- )
- self.song =
- song
- local ok, reason =
- self.cpu:call(
- self.tune.effectiveInitAddress,
- song - 1,
- 0,
- 0
- )
- if not ok then
- print(
- "Warning: init CPU: " ..
- tostring(reason)
- )
- end
- self.audio:resetChannels()
- -- For playAddress == 0, the init routine is expected to
- -- have installed the IRQ vector at $0314/$0315.
- if self.irqMode then
- local irqLo =
- self.memory[0x0314]
- local irqHi =
- self.memory[0x0315]
- local irqVector =
- (irqLo or 0) +
- bit.lshift(
- irqHi or 0,
- 8
- )
- if irqVector == 0 then
- print(
- "Warning: play address is $0000, " ..
- "but the init routine did not install a non-zero IRQ vector at $0314/$0315."
- )
- else
- print(
- string.format(
- "IRQ playback vector: $%04X",
- irqVector
- )
- )
- end
- end
- end
- function Machine:playFrame(frameMilliseconds)
- local ok
- local reason
- if self.tune.playAddress == 0 then
- --------------------------------------------------------
- -- PSID/RSID playAddress == 0:
- --
- -- The init routine installed its own IRQ handler.
- -- Execute one synthetic hardware IRQ using $0314/$0315.
- --------------------------------------------------------
- ok, reason =
- self.cpu:interrupt(
- 0x0314
- )
- else
- --------------------------------------------------------
- -- Normal PSID:
- -- call play routine directly.
- --------------------------------------------------------
- ok, reason =
- self.cpu:call(
- self.tune.playAddress,
- 0,
- 0,
- 0
- )
- end
- if not ok then
- return false, reason
- end
- self.audio:render(
- self.pal,
- frameMilliseconds
- )
- return true
- end
- ----------------------------------------------------------------
- -- Display information
- ----------------------------------------------------------------
- local function printInfo(tune)
- print("")
- print("========================================")
- print("OC-SID! A Sid Player for OpenComputers!")
- print("========================================")
- print("")
- print("Format : " .. tune.magic)
- print("Version: " .. tostring(tune.version))
- print("Songs : " .. tostring(tune.songs))
- print("Start : " .. tostring(tune.startSong))
- print(
- string.format(
- "Load : $%04X",
- tune.loadAddress
- )
- )
- print(
- string.format(
- "Init : $%04X",
- tune.effectiveInitAddress
- )
- )
- if tune.initAddress == 0 then
- print(
- " (effective load address)"
- )
- end
- print(
- string.format(
- "Play : $%04X",
- tune.playAddress
- )
- )
- print("----------------------------------------")
- print(
- "Clock : " ..
- tostring(tune.clock)
- )
- print(
- "SID #1 : " ..
- tostring(tune.sidModel1) ..
- " @ $D400"
- )
- if tune.secondSID ~= 0 then
- print(
- string.format(
- "SID #2 : %s @ $%04X",
- tostring(tune.sidModel2),
- tune.secondSID
- )
- )
- else
- print(
- "SID #2 : none"
- )
- end
- if tune.thirdSID ~= 0 then
- print(
- string.format(
- "SID #3 : %s @ $%04X",
- tostring(tune.sidModel3),
- tune.thirdSID
- )
- )
- print(
- " detected but not synthesized"
- )
- end
- print("----------------------------------------")
- if tune.playAddress == 0 then
- print(
- "Playback: installed IRQ handler"
- )
- else
- print(
- "Playback: direct play routine"
- )
- end
- print("")
- print("Space = pause/resume")
- print("+/- = volume")
- print("Q = quit")
- print("========================================")
- print("")
- end
- ----------------------------------------------------------------
- -- Main
- ----------------------------------------------------------------
- local args = {...}
- if not args[1] then
- print("Usage:")
- print(" sid.lua /path/to/file.sid")
- print(" sid.lua /path/to/file.sid 2")
- return
- end
- local filename =
- args[1]
- local requestedSong =
- tonumber(args[2])
- local tune =
- SID.loadFile(filename)
- ----------------------------------------------------------------
- -- Format warnings
- ----------------------------------------------------------------
- if tune.magic == "RSID" then
- print("")
- print("WARNING: RSID detected.")
- print(
- "RSID tunes require a much more complete C64 environment."
- )
- print(
- "VIC/CIA I/O, ROM behavior and cycle timing are not fully emulated."
- )
- print("")
- end
- if tune.thirdSID ~= 0 then
- print("")
- print(
- "WARNING: This tune declares a third SID."
- )
- print(
- "This player currently synthesizes SID #1 and SID #2 only."
- )
- print(
- string.format(
- "Third SID address: $%04X",
- tune.thirdSID
- )
- )
- print("")
- end
- ----------------------------------------------------------------
- -- Create machine
- ----------------------------------------------------------------
- local machine =
- Machine.new(tune)
- printInfo(tune)
- machine:load()
- local song =
- requestedSong or
- tune.startSong
- if song < 1 or
- song > tune.songs then
- error(
- "Invalid song number. Valid range: 1-" ..
- tostring(tune.songs)
- )
- end
- machine:init(song)
- ----------------------------------------------------------------
- -- Determine playback speed
- ----------------------------------------------------------------
- local songBit =
- song - 1
- local speedBit = 0
- if songBit < 32 then
- speedBit =
- bit.band(
- bit.rshift(
- tune.speed,
- songBit
- ),
- 1
- )
- end
- local usingCIA =
- speedBit ~= 0
- if usingCIA then
- ------------------------------------------------------------
- -- This remains an approximation.
- --
- -- Full CIA Timer A emulation requires considerably more
- -- C64 hardware state. Keep the existing behavior rather
- -- than making an inaccurate cycle-level implementation.
- ------------------------------------------------------------
- if tune.clock == "PAL" then
- machine:setClock(true)
- else
- machine:setClock(false)
- end
- machine.frameRate =
- NTSC_HZ
- else
- if tune.clock == "NTSC" then
- machine:setClock(false)
- elseif tune.clock == "PAL" then
- machine:setClock(true)
- elseif tune.clock == "PAL+NTSC" then
- machine:setClock(true)
- else
- machine:setClock(true)
- end
- end
- local frameMilliseconds =
- 1000 /
- machine.frameRate
- local frameDelay =
- 1 /
- machine.frameRate
- ----------------------------------------------------------------
- -- Playback information
- ----------------------------------------------------------------
- if usingCIA then
- print(
- string.format(
- "Playing song %d/%d using CIA-timing approximation at %d Hz.",
- song,
- tune.songs,
- machine.frameRate
- )
- )
- print(
- "Note: CIA timer interrupts are not cycle-emulated."
- )
- else
- print(
- string.format(
- "Playing song %d/%d at %d Hz.",
- song,
- tune.songs,
- machine.frameRate
- )
- )
- end
- print(
- string.format(
- "Sound frame: %.3f ms",
- frameMilliseconds
- )
- )
- if machine.sidCount >= 2 then
- print(
- "Dual SID audio: channels 1-3 = SID #1, channels 4-6 = SID #2."
- )
- end
- print("")
- ----------------------------------------------------------------
- -- Main playback loop
- ----------------------------------------------------------------
- local paused = false
- local running = true
- local nextFrame =
- computer.uptime()
- while running do
- ------------------------------------------------------------
- -- Paused
- ------------------------------------------------------------
- if paused then
- local e1,
- e2,
- character =
- event.pull(0.05)
- if e1 == "key_down" then
- if character == 113 or
- character == 81 then
- running = false
- elseif character == 32 then
- paused = false
- nextFrame =
- computer.uptime()
- end
- end
- else
- local now =
- computer.uptime()
- local wait =
- nextFrame - now
- --------------------------------------------------------
- -- Wait for next frame / keyboard input
- --------------------------------------------------------
- if wait > 0 then
- local e1,
- e2,
- character =
- event.pull(wait)
- if e1 == "key_down" then
- ------------------------------------------------
- -- Quit
- ------------------------------------------------
- if character == 113 or
- character == 81 then
- running = false
- ------------------------------------------------
- -- Pause
- ------------------------------------------------
- elseif character == 32 then
- paused = true
- ------------------------------------------------
- -- Volume up
- ------------------------------------------------
- elseif character == 43 or
- character == 61 then
- machine.audio:setVolume(
- machine.audio.volume +
- 0.05
- )
- print(
- string.format(
- "Volume: %d%%",
- math.floor(
- machine.audio.volume *
- 100 +
- 0.5
- )
- )
- )
- ------------------------------------------------
- -- Volume down
- ------------------------------------------------
- elseif character == 45 then
- machine.audio:setVolume(
- machine.audio.volume -
- 0.05
- )
- print(
- string.format(
- "Volume: %d%%",
- math.floor(
- machine.audio.volume *
- 100 +
- 0.5
- )
- )
- )
- end
- end
- --------------------------------------------------------
- -- Frame due
- --------------------------------------------------------
- else
- local ok, reason =
- machine:playFrame(
- frameMilliseconds
- )
- if not ok then
- print("")
- print(
- "Playback stopped: " ..
- tostring(reason)
- )
- running = false
- else
- ------------------------------------------------
- -- Keep frame timing stable.
- ------------------------------------------------
- nextFrame =
- nextFrame +
- frameDelay
- local current =
- computer.uptime()
- ------------------------------------------------
- -- If the emulator fell badly behind, resync
- -- instead of executing a huge backlog.
- ------------------------------------------------
- if current >
- nextFrame +
- frameDelay * 2 then
- nextFrame =
- current +
- frameDelay
- end
- end
- end
- end
- end
- ----------------------------------------------------------------
- -- Cleanup
- ----------------------------------------------------------------
- machine.audio:stop()
- print("")
- print("SID playback stopped.")
Advertisement