mirror of
https://github.com/g012/l65.git
synced 2024-12-29 16:29:15 +00:00
595 lines
25 KiB
Plaintext
595 lines
25 KiB
Plaintext
-- set cpu to 6502
|
|
cpu = require "6502"
|
|
setmetatable(_ENV, cpu)
|
|
|
|
vcs = {
|
|
-- TIA write only
|
|
VSYNC = 0x00, -- ......1. vertical sync set-clear
|
|
VBLANK = 0x01, -- 11....1. vertical blank set-clear
|
|
WSYNC = 0x02, -- <strobe> wait for leading edge of horizontal blank
|
|
RSYNC = 0x03, -- <strobe> reset horizontal sync counter
|
|
NUSIZ0 = 0x04, -- ..111111 number-size player-missile 0
|
|
NUSIZ1 = 0x05, -- ..111111 number-size player-missile 1
|
|
COLUP0 = 0x06, -- 1111111. color-lum player 0 and missile 0
|
|
COLUP1 = 0x07, -- 1111111. color-lum player 1 and missile 1
|
|
COLUPF = 0x08, -- 1111111. color-lum playfield and ball
|
|
COLUBK = 0x09, -- 1111111. color-lum background
|
|
CTRLPF = 0x0a, -- ..11.111 control playfield ball size & collisions
|
|
REFP0 = 0x0b, -- ....1... reflect player 0
|
|
REFP1 = 0x0c, -- ....1... reflect player 1
|
|
PF0 = 0x0d, -- 1111.... playfield register byte 0
|
|
PF1 = 0x0e, -- 11111111 playfield register byte 1
|
|
PF2 = 0x0f, -- 11111111 playfield register byte 2
|
|
RESP0 = 0x10, -- <strobe> reset player 0
|
|
RESP1 = 0x11, -- <strobe> reset player 1
|
|
RESM0 = 0x12, -- <strobe> reset missile 0
|
|
RESM1 = 0x13, -- <strobe> reset missile 1
|
|
RESBL = 0x14, -- <strobe> reset ball
|
|
AUDC0 = 0x15, -- ....1111 audio control 0
|
|
AUDC1 = 0x16, -- ....1111 audio control 1
|
|
AUDF0 = 0x17, -- ...11111 audio frequency 0
|
|
AUDF1 = 0x18, -- ...11111 audio frequency 1
|
|
AUDV0 = 0x19, -- ....1111 audio volume 0
|
|
AUDV1 = 0x1a, -- ....1111 audio volume 1
|
|
GRP0 = 0x1b, -- 11111111 graphics player 0
|
|
GRP1 = 0x1c, -- 11111111 graphics player 1
|
|
ENAM0 = 0x1d, -- ......1. graphics (enable) missile 0
|
|
ENAM1 = 0x1e, -- ......1. graphics (enable) missile 1
|
|
ENABL = 0x1f, -- ......1. graphics (enable) ball
|
|
HMP0 = 0x20, -- 1111.... horizontal motion player 0
|
|
HMP1 = 0x21, -- 1111.... horizontal motion player 1
|
|
HMM0 = 0x22, -- 1111.... horizontal motion missile 0
|
|
HMM1 = 0x23, -- 1111.... horizontal motion missile 1
|
|
HMBL = 0x24, -- 1111.... horizontal motion ball
|
|
VDELP0 = 0x25, -- .......1 vertical delay player 0
|
|
VDELP1 = 0x26, -- .......1 vertical delay player 1
|
|
VDELBL = 0x27, -- .......1 vertical delay ball
|
|
RESMP0 = 0x28, -- ......1. reset missile 0 to player 0
|
|
RESMP1 = 0x29, -- ......1. reset missile 1 to player 1
|
|
HMOVE = 0x2a, -- <strobe> apply horizontal motion
|
|
HMCLR = 0x2b, -- <strobe> clear horizontal motion registers
|
|
CXCLR = 0x2c, -- <strobe> clear collision latches= 0x20
|
|
|
|
-- TIA read only
|
|
CXM0P = 0x30, -- 11...... read collision M0-P1, M0-P0 (Bit 7 --6)
|
|
CXM1P = 0x31, -- 11...... read collision M1-P0, M1-P1
|
|
CXP0FB = 0x32, -- 11...... read collision P0-PF, P0-BL
|
|
CXP1FB = 0x33, -- 11...... read collision P1-PF, P1-BL
|
|
CXM0FB = 0x34, -- 11...... read collision M0-PF, M0-BL
|
|
CXM1FB = 0x35, -- 11...... read collision M1-PF, M1-BL
|
|
CXBLPF = 0x36, -- 1....... read collision BL-PF, unused
|
|
CXPPMM = 0x37, -- 11...... read collision P0-P1, M0-M1
|
|
INPT0 = 0x38, -- 1....... read pot port
|
|
INPT1 = 0x39, -- 1....... read pot port
|
|
INPT2 = 0x3a, -- 1....... read pot port
|
|
INPT3 = 0x3b, -- 1....... read pot port
|
|
INPT4 = 0x3c, -- 1....... read input
|
|
INPT5 = 0x3d, -- 1....... read input
|
|
|
|
-- PIA
|
|
SWCHA = 0x280, -- 11111111 Port A= 0x280 -- input or output (read or write)
|
|
SWACNT = 0x281, -- 11111111 Port A DDR, 0= input, 1=output
|
|
SWCHB = 0x282, -- 11111111 Port B= 0x280 -- console switches (read only)
|
|
SWBCNT = 0x283, -- 11111111 Port B DDR (hardwired as input)
|
|
INTIM = 0x284, -- 11111111 Timer output (read only)
|
|
INSTAT = 0x285, -- 11...... Timer Status (read only, undocumented)
|
|
|
|
TIM1T = 0x294, -- 11111111 set 1 clock interval (838 nsec/interval)
|
|
TIM8T = 0x295, -- 11111111 set 8 clock interval (6.7 usec/interval)
|
|
TIM64T = 0x296, -- 11111111 set 64 clock interval (53.6 usec/interval)
|
|
T1024T = 0x297, -- 11111111 set 1024 clock interval (858.2 usec/interval)
|
|
}
|
|
do
|
|
local symbols = cpu.symbols
|
|
for k,v in pairs(vcs) do symbols[k] = v end
|
|
end
|
|
|
|
nusiz = {
|
|
MSL_SIZE_1 = 16*0,
|
|
MSL_SIZE_2 = 16*1,
|
|
MSL_SIZE_4 = 16*2,
|
|
MSL_SIZE_8 = 16*3,
|
|
ONE_COPY = 0,
|
|
TWO_COPIES_CLOSE = 1,
|
|
TWO_COPIES_MEDIUM = 2,
|
|
THREE_COPIES_CLOSE = 3,
|
|
TWO_COPIES_WIDE = 4,
|
|
DOUBLE_SIZED_PLAYER = 5,
|
|
THREE_COPIES_MEDIUM = 6,
|
|
QUAD_SIZED_PLAYER = 7,
|
|
}
|
|
|
|
ctrlpf = {
|
|
BALL_SIZE_1 = 16*0,
|
|
BALL_SIZE_2 = 16*1,
|
|
BALL_SIZE_4 = 16*2,
|
|
BALL_SIZE_8 = 16*3,
|
|
PF_MIRRORED = 1,
|
|
PF_SCOREMODE = 2,
|
|
PF_PRIO_BALLABOVE = 4,
|
|
}
|
|
|
|
enable = {
|
|
DISABLE = 0,
|
|
ENABLE = 2,
|
|
}
|
|
|
|
vdel = {
|
|
DISABLE = 0,
|
|
ENABLE = 1,
|
|
}
|
|
|
|
hm = {
|
|
LEFT_7 = 7*16,
|
|
LEFT_6 = 6*16,
|
|
LEFT_5 = 5*16,
|
|
LEFT_4 = 4*16,
|
|
LEFT_3 = 3*16,
|
|
LEFT_2 = 2*16,
|
|
LEFT_1 = 1*16,
|
|
NO_MOTION = 0,
|
|
RIGHT_1 = 15*16,
|
|
RIGHT_2 = 14*16,
|
|
RIGHT_3 = 13*16,
|
|
RIGHT_4 = 12*16,
|
|
RIGHT_5 = 11*16,
|
|
RIGHT_6 = 10*16,
|
|
RIGHT_7 = 9*16,
|
|
RIGHT_8 = 8*16,
|
|
|
|
C74_LEFT_15 = 7*16,
|
|
C74_LEFT_14 = 6*16,
|
|
C74_LEFT_13 = 5*16,
|
|
C74_LEFT_12 = 4*16,
|
|
C74_LEFT_11 = 3*16,
|
|
C74_LEFT_10 = 2*16,
|
|
C74_LEFT_9 = 1*16,
|
|
C74_LEFT_8 = 0*16,
|
|
C74_LEFT_7 = 15*16,
|
|
C74_LEFT_6 = 14*16,
|
|
C74_LEFT_5 = 13*16,
|
|
C74_LEFT_4 = 12*16,
|
|
C74_LEFT_3 = 11*16,
|
|
C74_LEFT_2 = 10*16,
|
|
C74_LEFT_1 = 9*16,
|
|
C74_NO_MOTION = 8*16,
|
|
}
|
|
|
|
NTSC = {
|
|
TIM_OVERSCAN = 38, -- TIM64T, 2432 cycles = 32 scanlines
|
|
TIM_VBLANK = 45, -- TIM64T, 2880 cycles = ~ 38 scanlines
|
|
TIM_KERNEL = 15, -- T1024T, 15360 cycles = ~202 scanlines
|
|
}
|
|
PAL = {
|
|
TIM_OVERSCAN = 50, -- TIM64T, 3200 cycles = ~ 42 scanlines
|
|
TIM_VBLANK = 61, -- TIM64T, 3904 cycles = ~ 51 scanlines
|
|
TIM_KERNEL = 17, -- T1024T, 17408 cycles = ~229 scanlines
|
|
}
|
|
TV = PAL
|
|
|
|
init = function() cld ldx#0 txa local l=label() dex tsx pha bne l end
|
|
wait = function() local l=label() lda INTIM bne l end
|
|
overscan_begin = function() sta WSYNC lda#2 sta VBLANK lda#TV.TIM_OVERSCAN sta TIM64T end
|
|
overscan_end = wait
|
|
overscan = function(f) overscan_begin() if f then f() end overscan_end() end
|
|
vblank_begin = function()
|
|
lda#0b1110 local l=label() sta WSYNC sta VSYNC lsr bne l
|
|
lda#2 sta VBLANK lda#TV.TIM_VBLANK sta TIM64T
|
|
end
|
|
vblank_end = function() wait() sta WSYNC sta VBLANK end
|
|
vblank = function(f) vblank_begin() if f then f() end vblank_end() end
|
|
screen_begin = function() lda#TV.TIM_KERNEL sta T1024T end
|
|
screen_end = wait
|
|
screen = function(f) screen_begin() if f then f() end screen_end() end
|
|
|
|
setptr = function(f, add) if not add then add=tmp end lda#op_resolve(f)&0xff sta add lda#op_resolve(f)>>8 sta add+1 end
|
|
xcall = function(f) setptr(f, mappers.tmp) jsr jmpfar end
|
|
rtximp = function() jmp rtsfar end
|
|
|
|
-- for mappers that swap banks in place
|
|
-- call an asm function into another bank and generate the stubs if needed
|
|
local far_stubs = {} _ENV.far_stubs=far_stubs
|
|
farcall = function(dst)
|
|
local loc_src = location_current
|
|
local create_stubs = function()
|
|
if type(dst) == 'function' then dst = dst() end
|
|
local stub,loc_dst = far_stubs[dst],dst.section.location
|
|
if not (stub and stub[loc_src]) then
|
|
location(loc_src)
|
|
local stub_caller = section() switchrom(loc_dst.rom) skip(6) rts
|
|
location(loc_dst)
|
|
local stub_callee = section() jsr dst switchrom(loc_src.rom)
|
|
relate(stub_caller, stub_callee, 3)
|
|
if not stub then far_stubs[dst] = {} end
|
|
far_stubs[dst][loc_src] = stub_caller
|
|
end
|
|
end
|
|
-- create stubs at the end to get to the referenced labels
|
|
table.insert(before_link, create_stubs)
|
|
jsr far_stubs[dst][loc_src]
|
|
end
|
|
|
|
mappers = { tmp=0x8a }
|
|
|
|
mappers['2K'] = function()
|
|
rom0 = location(0xf800, 0xffff)
|
|
section{"vectors", org=0xfffc} dc.w main if mappers.irq then dc.w main end
|
|
end
|
|
mappers.CV = mappers['2K']
|
|
|
|
mappers['4K'] = function()
|
|
rom0 = location(0xf000, 0xffff)
|
|
section{"vectors", org=0xfffc} dc.w main if mappers.irq then dc.w main end
|
|
end
|
|
|
|
local bank_stubs = function(count, hotspot, entry)
|
|
function switchrom(i) assert(i>=0 and i<count) if mappers.noillegal then bit 0x1000+hotspot+i else nop 0x1000+hotspot+i end end
|
|
local base = 0x10000 - (count << 12)
|
|
local jmpfar0,rtsfar0
|
|
for bi=0,count-1 do
|
|
local o,ro = base+(bi<<12), mappers.jmpfar and 0x1000+(bi<<13) or 0xf000 -- stella doesn't honor breakpoints in banks with jmpfar method
|
|
_ENV['rom' .. bi] = location{o, o+0xfff, rorg=ro, rom=bi}
|
|
local start=section{"entry"..bi, org=o+(entry or hotspot-6)} switchrom(0) if bi==0 then jmp main end
|
|
section{"switchtab"..bi, org=o+hotspot} for i=1,count do byte(0) end
|
|
section{"vectors"..bi, org=o+0xffc} dc.w start if mappers.irq then dc.w start end
|
|
if mappers.jmpfar then
|
|
local jmpfar = section("jmpfar"..(bi>0 and bi or ''))
|
|
lda mappers.tmp+1 lsr lsr lsr lsr lsr tax
|
|
if mappers.noillegal then and 0x1000+hotspot,x else nop 0x1000+hotspot,x end
|
|
jmp (mappers.tmp)
|
|
local rtsfar = section("rtsfar"..(bi>0 and bi or ''))
|
|
tsx lda 2,x lsr lsr lsr lsr lsr tax
|
|
if mappers.noillegal then and 0x1000+hotspot,x else nop 0x1000+hotspot,x end
|
|
rts
|
|
if bi==0 then jmpfar0=jmpfar rtsfar0=rtsfar
|
|
else relate(jmpfar0, jmpfar) relate(rtsfar0, rtsfar) end
|
|
end
|
|
end
|
|
end
|
|
mappers.FA = function() bank_stubs(3, 0xff8) end
|
|
mappers.F8 = function() bank_stubs(2, 0xff8) end
|
|
mappers.F6 = function() bank_stubs(4, 0xff6) end
|
|
mappers.F4 = function() bank_stubs(8, 0xff4) end
|
|
mappers.EF = function() bank_stubs(16, 0xfe0, 0xffc-6) end
|
|
|
|
mappers.FE = function()
|
|
rom1 = location(0xd000, 0xdfff)
|
|
section{"vectors1", org=0xdffc} dc.w main if mappers.irq then dc.w main end
|
|
location{0xe000, 0xefff, nofill=true}
|
|
rom0 = location(0xf000, 0xffff)
|
|
section{"vectors0", org=0xfffc} dc.w main if mappers.irq then dc.w main end
|
|
end
|
|
|
|
mappers.E0 = function(map)
|
|
function switchrom0(i) assert(map[i]==0) if mappers.noillegal then bit 0x1fe0+i else nop 0x1fe0+i end end
|
|
function switchrom1(i) assert(map[i]==1) if mappers.noillegal then bit 0x1fe8+i else nop 0x1fe8+i end end
|
|
function switchrom2(i) assert(map[i]==2) if mappers.noillegal then bit 0x1ff0+i else nop 0x1ff0+i end end
|
|
map[7]=3
|
|
for bi=0,7 do
|
|
local o = bi*0x400
|
|
_ENV["rom"..bi] = location{0xe000+o, 0xe3ff+o, rorg=0xf000+map[bi]*0x400}
|
|
end
|
|
section{"switchtab", org=0xffe0} for i=1,8*3 do byte(0) end
|
|
section{"vectors", org=0xfffc} dc.w main if mappers.irq then dc.w main end
|
|
end
|
|
|
|
-- rom0 refers to the last one in ROM, ie the one always mapped to 0xF800-0xFFFF,
|
|
-- such that changing the rom count does not change its index
|
|
mappers['3F'] = function(count)
|
|
function switchrom(i) assert(i>=0 and i<count-1) lda#i sta 0x3f end
|
|
local symbols=cpu.symbols for k,v in pairs(vcs) do -- remap TIA to 0x40-0x7F
|
|
if v<0x40 then v=v+0x40 vcs[k]=v symbols[k]=v end
|
|
end
|
|
bi=0,count-2 do
|
|
local o = 0x800*bi + 0x10000
|
|
_ENV["rom"..(count-1-bi)] = location{o, 0x7ff+o, rorg=0xf000}
|
|
end
|
|
local o = 0x800 * (count-1) + 0x10000
|
|
rom0 = location{o, 0x7ff+o, rorg=0xf800}
|
|
section{"vectors", org=0xfffc} dc.w main if mappers.irq then dc.w main end
|
|
end
|
|
|
|
mappers.E7 = function()
|
|
function switchrom(i) assert(i>=0 and i<7) if mappers.noillegal then bit 0x1fe0+i else nop 0x1fe0+i end end
|
|
function enableram() if mappers.noillegal then bit 0x1fe7 else nop 0x1fe7 end end
|
|
function switchram(i) assert(i>=0 and i<4) if mappers.noillegal then bit 0x1fe8+i else nop 0x1fe8+i end end
|
|
for bi=0,6 do
|
|
local o = bi*0x800
|
|
_ENV['rom' .. bi] = location{o+0xc000, o+0xc7ff, rorg=0xf000}
|
|
end
|
|
rom7 = location{0xf800, 0xffff, rorg=0xf800}
|
|
section{"switchtab", org=0xffe0} for i=1,12 do byte(0) end
|
|
section{"vectors", org=0xfffc} dc.w main if mappers.irq then dc.w main end
|
|
end
|
|
|
|
mappers.F0 = function()
|
|
function switchrom() lda 0x1ff0 end
|
|
for bi=0,15 do
|
|
local o = bi*0x1000 + 0x1000
|
|
_ENV['rom' .. bi] = location{o, o+0xfff, rorg=0xf000}
|
|
local start=section{"entry"..bi, org=o+0xffc-8}
|
|
local l=label() switchrom() bne l
|
|
if bi==0 then jmp main end
|
|
section{"switchtab"..bi, org=o+0xff0} byte(bi)
|
|
section{"vectors"..bi, org=o+0xffc} dc.w start if mappers.irq then dc.w start end
|
|
end
|
|
end
|
|
|
|
local bank_stubs2 = function(hotspot0, hotspot1)
|
|
function switchrom(i)
|
|
if i==0 then if mappers.noillegal then bit hotspot0 else nop hotspot0 end
|
|
elseif i==1 then if mappers.noillegal then bit hotspot1 else nop hotspot1 end
|
|
else error("invalid rom index: " .. i) end
|
|
end
|
|
local base = 0xe000
|
|
for bi=0,1 do
|
|
local o = base+(bi<<12)
|
|
_ENV['rom' .. bi] = location{o, o+0xfff, rorg=0xf000}
|
|
local start=section{"entry"..bi, org=o+0xffc-6} switchrom(0) if bi==0 then jmp main end
|
|
section{"vectors"..bi, org=o+0xffc} dc.w start if mappers.irq then dc.w start end
|
|
end
|
|
end
|
|
mappers.UA = function() bank_stubs2(0x220, 0x240) end
|
|
mappers['0840'] = function() bank_stubs2(0x800, 0x840) end
|
|
mappers.SB = function(count)
|
|
function switchrom(i) assert(i>=0 and i<count) if mappers.noillegal then bit 0x800+i else nop 0x800+i end end
|
|
for bi=0,count-1 do
|
|
local o = bi*0x1000 + 0x1000
|
|
_ENV['rom' .. bi] = location{o, o+0xfff, rorg=0xf000}
|
|
local start=section{"entry"..bi, org=o+0xffc-6} switchrom(0) if bi==0 then jmp main end
|
|
section{"vectors"..bi, org=o+0xffc} dc.w start if mappers.irq then dc.w start end
|
|
end
|
|
end
|
|
|
|
mappers['3E'] = function(rom_count, ram_count)
|
|
mappers['3F'](rom_count)
|
|
function switchram(i) assert(i>=0 and i<ram_count-1) lda#i sta 0x3E end
|
|
end
|
|
|
|
-- rom0 refers to the last 1k in ROM, which is mapped automatically on reset,
|
|
-- such that changing the rom count does not change its index
|
|
mappers.MC = function(rom_count, ram_count)
|
|
for i=0,3 do
|
|
_ENV['switchrom'..i] = function(i) assert(i>=0 and i<128) lda#i sta 0x3c+i end
|
|
_ENV['switchram'..i] = function(i) assert(i>=0 and i<128) lda#i+0x80 sta 0x3c+i end
|
|
end
|
|
for bi=0,rom_count-1 do
|
|
local o = bi*0x400 + 0x1000
|
|
_ENV["rom"..(count-1-bi)] = location{o, 0x3ff+o, rorg=0xf000+(o&0xfff)}
|
|
end
|
|
local start=section("entry") switchrom3(0) jmp main
|
|
section{"vectors", org=0xfffc} dc.w start if mappers.irq then dc.w start end
|
|
end
|
|
|
|
mappers.X07 = function()
|
|
function switchrom(i) assert(i>=0 and i<16) if mappers.noillegal then bit 0x80b+(i<<4) else nop 0x80b+(i<<4) end end
|
|
-- map TIA also to 0x40-0x7F: use VSYNC for bank 14, and VSYNC2 for bank 15
|
|
vcs.VSYNC2=0x40 cpu.symbols.VSYNC2=0x40
|
|
for bi=0,count-1 do
|
|
local o = bi*0x1000 + 0x1000
|
|
_ENV['rom' .. bi] = location{o, o+0xfff, rorg=0xf000}
|
|
local start=section{"entry"..bi, org=o+0xffc-6} switchrom(0) if bi==0 then jmp main end
|
|
section{"vectors"..bi, org=o+0xffc} dc.w start if mappers.irq then dc.w start end
|
|
end
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Image converters
|
|
--------------------------------------------------------------------------------
|
|
|
|
image_scan = function(img, opt)
|
|
local r, b, flipx, flipy, xinc, yinc, x0, y0, x1, y1, width, height = {}, {}
|
|
xinc=opt.xinc or 1 x0=opt.x0 or 0 x1=opt.x1 or img.width-1
|
|
yinc=opt.yinc or 1 y0=opt.y0 or 0 y1=opt.y1 or img.height-1
|
|
if x1<x0 and xinc<0 then x0,x1=x1,x0 xinc=-xinc flipx=true end
|
|
if y1<y0 and yinc<0 then y0,y1=y1,y0 yinc=-yinc flipy=true end
|
|
assert(y1>=y0 and x1>=x0 and x1<img.width and y1<img.height,
|
|
string.format("invalid dimensions: x[%d -> %d] y[%d -> %d] for img[%d, %d]", x0, x1, y0, y1, img.width, img.height))
|
|
width = 1 + (x1 - x0) // xinc
|
|
height = 1 + (y1 - y0) // yinc
|
|
for y=y0,y1,yinc do for x=x0,x1,xinc do b[#b+1] = img[y*img.width+x+1] end end
|
|
if flipx then for y=0,height-1 do for x=0,width//2-1 do
|
|
local i0, i1 = y*width+x+1, y*height+width
|
|
b[i0], b[i1] = b[i1], b[i0]
|
|
end end end
|
|
if flipy then for y=0,height//2-1 do for x=0,width-1 do
|
|
local i0, i1 = y*width+x+1, (height-1-y)*width+x+1
|
|
b[i0], b[i1] = b[i1], b[i0]
|
|
end end end
|
|
return b, width, height
|
|
end
|
|
|
|
linecol = function(opt)
|
|
if type(opt) ~= 'table' then opt = { opt } end
|
|
local image = opt.image or assert(l65.image(opt.filename or opt[1]))
|
|
local b,w,h = image_scan(image, opt)
|
|
local lc = {}
|
|
for y=0,h-1 do
|
|
lc[#lc+1] = 0
|
|
for x=1,w do
|
|
local col = b[y*w + x]
|
|
if col ~= 0 then lc[#lc]=col break end
|
|
end
|
|
end
|
|
return lc
|
|
end
|
|
|
|
playfield = function(opt)
|
|
if type(opt) ~= 'table' then opt = { opt } end
|
|
local image = opt.image or assert(l65.image(opt.filename or opt[1]))
|
|
local b,w,h = image_scan(image, opt)
|
|
local pf={} for i=1,6 do pf[i]={} end
|
|
for y=1,h do
|
|
local x = \o,s((b[(y-1)*w+o+1]==0 and 0 or 1)<<s)
|
|
pf[1][y] = x( 0,4)|x( 1,5)|x( 2,6)|x( 3,7)
|
|
pf[2][y] = x( 4,7)|x( 5,6)|x( 6,5)|x( 7,4)|x( 8,3)|x( 9,2)|x(10,1)|x(11,0)
|
|
pf[3][y] = x(12,0)|x(13,1)|x(14,2)|x(15,3)|x(16,4)|x(17,5)|x(18,6)|x(19,7)
|
|
pf[4][y] = x(20,4)|x(21,5)|x(22,6)|x(23,7)
|
|
pf[5][y] = x(24,7)|x(25,6)|x(26,5)|x(27,4)|x(28,3)|x(29,2)|x(30,1)|x(31,0)
|
|
pf[6][y] = x(32,0)|x(33,1)|x(34,2)|x(35,3)|x(36,4)|x(37,5)|x(38,6)|x(39,7)
|
|
end
|
|
if opt.reverse then
|
|
for _,col in ipairs(pf) do for y=1,h do col[y] = col[y] ~ 0xff end end
|
|
end
|
|
return pf
|
|
end
|
|
|
|
sprite = function(opt)
|
|
if type(opt) ~= 'table' then opt = { opt } end
|
|
local image = opt.image or assert(l65.image(opt.filename or opt[1]))
|
|
local b,w,h = image_scan(image, opt)
|
|
local sp={}
|
|
for c=0,((w+7)//8-1) do
|
|
local s={} sp[#sp+1]=s
|
|
for y=0,h-1 do
|
|
local o=c*8
|
|
local e=o+7 >= w and w-1 or o+7
|
|
local i,color = 7,0
|
|
for x=o,e do
|
|
if b[y*w+x+1] ~= 0 then color = color | 1<<i end
|
|
i = i-1
|
|
end
|
|
s[#s+1] = color
|
|
end
|
|
end
|
|
return sp
|
|
end
|
|
|
|
-- load a TIATracker song and prepare the data for VCS playback
|
|
ttt = function(filename)
|
|
local f, str = assert(io.open(filename,'r')) str=f:read('*all') f:close()
|
|
local s, pos, err = require"dkjson".decode(str)
|
|
if err then error(string.format("error parsing JSON file %s: %s", filename, err)) end
|
|
|
|
local t = {}
|
|
t.version = s.version
|
|
t.tvmode = s.tvmode
|
|
t.rowsperbeat = s.rowsperbeat
|
|
t.author = s.metaAuthor
|
|
t.name = s.metaName
|
|
t.comment = s.metaComment
|
|
t.globalspeed = s.globalspeed == nil or s.globalspeed
|
|
t.speed = s.evenspeed
|
|
t.oddspeed = s.oddspeed
|
|
if t.globalspeed then t.usefunktempo = s.evenspeed ~= s.oddspeed
|
|
else
|
|
for k,v in ipairs(s.channels[1].sequence) do
|
|
local pattern = s.patterns[v.patternindex+1]
|
|
if pattern.evenspeed ~= pattern.oddspeed then t.usefunktempo=true break end
|
|
end
|
|
end
|
|
t.usegoto,t.useslide,t.useoverlay,t.startswithnotes = false,false,false,true
|
|
|
|
local patternmap = {}
|
|
local instrumentmap,instrumentcount = {},0
|
|
local percussionmap,percussioncount = {},0
|
|
t.insctrltable, t.insadindexes, t.inssustainindexes, t.insreleaseindexes = {}, {}, {}, {}
|
|
t.percfreqtable, t.percctrlvoltable, t.percindexes = {}, {}, {}
|
|
local insEnvelopeIndex,percEnvelopeIndex = 0,0
|
|
t.insfreqvoltable = {}
|
|
t.patternspeeds = {}
|
|
t.patterns,t.patternptr = {},{}
|
|
t.sequence = { {}, {} }
|
|
local ins = table.insert
|
|
for channelindex, channel in ipairs(s.channels) do
|
|
local gotooffset = 0
|
|
for sequenceindex,sequence in ipairs(channel.sequence) do
|
|
local patternindex = sequence.patternindex + 1
|
|
if not patternmap[patternindex] then
|
|
patternmap[patternindex] = #t.patterns
|
|
local spattern = s.patterns[patternindex]
|
|
local pattern = { name = spattern.name }
|
|
ins(t.patterns, pattern)
|
|
for noteindex,note in ipairs(spattern.notes) do
|
|
if note.type == 1 then -- Instrument
|
|
if not instrumentmap[note.number] then
|
|
instrumentmap[note.number] = instrumentcount
|
|
local instrument = s.instruments[note.number+1]
|
|
instrumentcount = instrumentcount + (instrument.waveform == 16 and 2 or 1)
|
|
local sz = instrument.envelopeLength
|
|
while sz > instrument.releaseStart+1 and instrument.frequencies[sz] == 0 and instrument.volumes[sz] == 0 do sz = sz-1 end
|
|
local oldsz = #t.insfreqvoltable
|
|
for i=1,sz do
|
|
ins(t.insfreqvoltable, instrument.frequencies[i]+8 << 4 | instrument.volumes[i])
|
|
end
|
|
ins(t.insfreqvoltable, instrument.releaseStart + 1 + oldsz, 0)
|
|
ins(t.insfreqvoltable, 0)
|
|
for i = 1, instrument.waveform == 16 and 2 or 1 do
|
|
ins(t.insadindexes, insEnvelopeIndex)
|
|
ins(t.inssustainindexes, insEnvelopeIndex + instrument.sustainStart)
|
|
ins(t.insreleaseindexes, insEnvelopeIndex + instrument.releaseStart)
|
|
end
|
|
insEnvelopeIndex = insEnvelopeIndex + sz + 2
|
|
if instrument.waveform == 16 then -- PURE_COMBINED
|
|
ins(t.insctrltable, 4) -- PURE_HIGH
|
|
ins(t.insctrltable, 12) -- PURE_LOW
|
|
else
|
|
ins(t.insctrltable, instrument.waveform)
|
|
end
|
|
end
|
|
local valueIns = instrumentmap[note.number] + 1
|
|
if s.instruments[note.number+1].waveform == 16 and note.value > 31 then valueIns=valueIns+1 end
|
|
ins(pattern, valueIns<<5 | note.value&0x1f)
|
|
elseif note.type == 3 then -- Percussion
|
|
if not percussionmap[note.number] then
|
|
percussionmap[note.number] = percussioncount
|
|
percussioncount = percussioncount + 1
|
|
local percussion = s.percussion[note.number+1]
|
|
local sz = percussion.envelopeLength
|
|
while sz > 0 and percussion.waveforms[sz] == 0 and percussion.volumes[sz] == 0 do sz = sz-1 end
|
|
for i=1,sz do
|
|
local freq = percussion.frequencies[i]
|
|
if percussion.overlay and i == sz then freq = freq + 128 end
|
|
ins(t.percfreqtable, freq)
|
|
ins(t.percctrlvoltable, percussion.waveforms[i] << 4 | percussion.volumes[i])
|
|
end
|
|
ins(t.percfreqtable, 0)
|
|
ins(t.percctrlvoltable, 0)
|
|
ins(t.percindexes, percEnvelopeIndex+1)
|
|
percEnvelopeIndex = percEnvelopeIndex + sz + 1
|
|
if percussion.overlay then t.useoverlay = true end
|
|
end
|
|
ins(pattern, percussionmap[note.number] + 17) -- NoteFirstPerc
|
|
elseif note.type == 0 then -- Hold
|
|
ins(pattern, 8) -- NoteHold
|
|
if sequenceindex == 1 and noteindex == 1 then t.startswithnotes = false end
|
|
elseif note.type == 2 then -- Pause
|
|
ins(pattern, 16) -- NotePause
|
|
elseif note.type == 4 then -- Slide
|
|
ins(pattern, 8 + note.value) -- NoteHold + note.value
|
|
t.useslide = true
|
|
end
|
|
end
|
|
ins(pattern, 0)
|
|
if not t.globalspeed then
|
|
ins(t.patternspeeds, t.usefunktempo and (spattern.evenspeed-1 << 4 | spattern.oddspeed-1) or spattern.evenspeed-1)
|
|
end
|
|
end
|
|
ins(t.sequence[channelindex], patternmap[patternindex])
|
|
if sequence.gototarget ~= -1 then
|
|
t.usegoto = true
|
|
local value = 128 + sequence.gototarget + gotooffset
|
|
gotooffset = gotooffset + 1
|
|
if channelindex == 2 then value = value + #t.sequence[1] end
|
|
assert(value < 256, "goto target in channel " .. (channelindex-1) .. " is out of range: " .. value)
|
|
ins(t.sequence[channelindex], value)
|
|
end
|
|
end
|
|
end
|
|
|
|
local i
|
|
t.c0init,i = s.startpattern0,0
|
|
while i <= t.c0init do
|
|
if t.sequence[1][i+1] > 127 then t.c0init = t.c0init + 1 end
|
|
i = i+1
|
|
end
|
|
t.c1init,i = s.startpattern1,0
|
|
while i <= t.c1init do
|
|
if t.sequence[2][i+1] > 127 then t.c1init = t.c1init + 1 end
|
|
i = i+1
|
|
end
|
|
t.c1init = t.c1init + #t.sequence[1]
|
|
|
|
return t
|
|
end
|