Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- local speaker = peripheral.find("speaker")
- if not speaker then error("No speaker attached!") end
- local SPEAKER_RATE = 48000
- local MIX_RATE = SPEAKER_RATE
- local TARGET_CHUNK = 16384
- local AMIGA_CLOCK = 3546895
- local INTERPOLATE = true
- local RAMP_SAMPLES = 64
- local SOFT_KNEE = 100
- local LOOP_SONG = true
- local MAX_BG_VOICES = 48
- local floor, min, max = math.floor, math.min, math.max
- local sin, sqrt = math.sin, math.sqrt
- local band, bor, lshift, rshift = bit32.band, bit32.bor, bit32.lshift, bit32.rshift
- local song
- local NOTE_OFF, NOTE_CUT, NOTE_FADE = -1, -2, -3
- local LE = 0x40
- local EMPTY = { n = 0, i = 0, vc = 0, vv = 0, e = 0, p = 0 }
- local EMPTYROW = {}
- local EMPTYPAT = { rows = 64 }
- local D = ""
- local function u8(p) return D:byte(p + 1) or 0 end
- local function s8(p) local v = u8(p); return v > 127 and v - 256 or v end
- local function u16(p) return u8(p) + u8(p + 1) * 256 end
- local function s16(p) local v = u16(p); return v > 32767 and v - 65536 or v end
- local function u32(p) return u16(p) + u16(p + 2) * 65536 end
- local function u16be(p) return u8(p) * 256 + u8(p + 1) end
- local function readS8(dp, len)
- local t, byte = {}, string.byte
- for j = 1, len do
- local b = byte(D, dp + j) or 0
- t[j] = b > 127 and b - 256 or b
- end
- return t
- end
- local function unrollPingPong(s, which)
- local ls, ll
- if which == "sus" then ls, ll = s.susStart, s.susLen else ls, ll = s.loopStart, s.loopLen end
- if ll < 2 then return end
- local d, le = s.data, ls + ll
- local nd = {}
- for i = 1, le do nd[i] = d[i] end
- local k = le
- for i = le - 1, ls + 2, -1 do
- k = k + 1
- nd[k] = d[i]
- end
- local newLen = k
- if which == "sus" then
- for i = le + 1, s.len do
- newLen = newLen + 1
- nd[newLen] = d[i]
- end
- s.susLen = k - ls
- else
- s.loopLen = k - ls
- end
- s.data, s.len = nd, newLen
- end
- local function signExt(v, w)
- local h = 2 ^ (w - 1)
- if v >= h then return v - 2 * h end
- return v
- end
- local function itDecompress(p, len, is16, it215)
- local out, n = {}, 0
- local blkMax = is16 and 0x4000 or 0x8000
- local startW = is16 and 17 or 9
- while n < len do
- if p >= #D then break end
- local clen = u16(p)
- p = p + 2
- local bp, bitbuf, bitcnt = p, 0, 0
- local function rb(nb)
- while bitcnt < nb do
- bitbuf = bitbuf + u8(bp) * 2 ^ bitcnt
- bp = bp + 1
- bitcnt = bitcnt + 8
- end
- local v = bitbuf % 2 ^ nb
- bitbuf = (bitbuf - v) / 2 ^ nb
- bitcnt = bitcnt - nb
- return v
- end
- local blk = min(blkMax, len - n)
- local width, d1, d2, cnt = startW, 0, 0, 0
- while cnt < blk do
- if width < 1 or width > startW then error("corrupt IT sample") end
- local value = rb(width)
- local handled = false
- if is16 then
- if width < 7 then
- if value == 2 ^ (width - 1) then
- local v = rb(4) + 1
- width = (v < width) and v or v + 1
- handled = true
- end
- elseif width < 17 then
- local border = floor(0xFFFF / 2 ^ (17 - width)) - 8
- if value > border and value <= border + 16 then
- local v = value - border
- width = (v < width) and v or v + 1
- handled = true
- end
- elseif value >= 0x10000 then
- width = (value + 1) % 256
- handled = true
- end
- else
- if width < 7 then
- if value == 2 ^ (width - 1) then
- local v = rb(3) + 1
- width = (v < width) and v or v + 1
- handled = true
- end
- elseif width < 9 then
- local border = floor(0xFF / 2 ^ (9 - width)) - 4
- if value > border and value <= border + 8 then
- local v = value - border
- width = (v < width) and v or v + 1
- handled = true
- end
- elseif value >= 0x100 then
- width = (value + 1) % 256
- handled = true
- end
- end
- if not handled then
- local v
- if is16 then
- v = (width < 16) and signExt(value, width) or signExt(value % 65536, 16)
- else
- v = (width < 8) and signExt(value, width) or signExt(value % 256, 8)
- end
- d1 = d1 + v
- d2 = d2 + d1
- if is16 then
- d1 = ((d1 + 32768) % 65536) - 32768
- d2 = ((d2 + 32768) % 65536) - 32768
- else
- d1 = ((d1 + 128) % 256) - 128
- d2 = ((d2 + 128) % 256) - 128
- end
- n = n + 1
- out[n] = it215 and d2 or d1
- cnt = cnt + 1
- end
- end
- p = p + clen
- end
- if is16 then
- for i = 1, #out do out[i] = out[i] / 256 end
- end
- for i = #out + 1, len do out[i] = 0 end
- return out
- end
- local function envValue(env, tk)
- local pts = env.pts
- local n = #pts
- if tk <= pts[1][1] then return pts[1][2] end
- for i = 2, n do
- local b = pts[i]
- if tk <= b[1] then
- local a = pts[i - 1]
- local dx = b[1] - a[1]
- if dx <= 0 then return b[2] end
- return a[2] + (b[2] - a[2]) * (tk - a[1]) / dx
- end
- end
- return pts[n][2]
- end
- local function advanceEnv(env, t, key)
- local pts = env.pts
- local pos = t[key]
- if env.sustain and not t.keyoff then
- local se = pts[env.susEnd][1]
- if pos >= se then
- t[key] = pts[env.susStart][1]
- return
- end
- end
- pos = pos + 1
- if env.loop and pos >= pts[env.loopEnd][1] then
- pos = pts[env.loopStart][1]
- end
- t[key] = pos
- end
- local function makeEnv(pts, on, sus, susStart, susEnd, loop, loopStart, loopEnd)
- if not on or #pts < 1 then return nil end
- local n = #pts
- local function cl(i) return max(1, min(n, i)) end
- return { pts = pts, sustain = sus, susStart = cl(susStart), susEnd = cl(susEnd),
- loop = loop, loopStart = cl(loopStart), loopEnd = cl(loopEnd) }
- end
- local function convertLetter(L, par)
- local isIT = song.fmt == "IT"
- local hi, lo = rshift(par, 4), band(par, 15)
- if L == 1 then return 0x30, par
- elseif L == 2 then return 0x0B, par
- elseif L == 3 then
- if isIT then return 0x3B, par end
- return 0x0D, par
- elseif L == 4 then return 0x38, par
- elseif L == 5 then
- if par >= 0xF0 then return 0x0E, 0x20 + lo
- elseif par >= 0xE0 then return 0x21, 0x20 + lo end
- return 0x02, par
- elseif L == 6 then
- if par >= 0xF0 then return 0x0E, 0x10 + lo
- elseif par >= 0xE0 then return 0x21, 0x10 + lo end
- return 0x01, par
- elseif L == 7 then return 0x03, par
- elseif L == 8 then return 0x04, par
- elseif L == 9 then return 0x1D, par
- elseif L == 10 then return 0x00, par
- elseif L == 11 then return 0x39, par
- elseif L == 12 then return 0x3A, par
- elseif L == 13 and isIT then return 0x2C, par
- elseif L == 14 and isIT then return 0x37, par
- elseif L == 15 then return 0x09, par
- elseif L == 17 then return 0x1B, par
- elseif L == 18 then return 0x07, par
- elseif L == 19 then
- if hi == 3 then return 0x0E, 0x40 + lo
- elseif hi == 4 then return 0x0E, 0x70 + lo
- elseif hi == 6 then return 0x3E, lo
- elseif hi == 7 then return 0x35, lo
- elseif hi == 0xA then return 0x36, lo
- elseif hi >= 0xB and hi <= 0xE then return 0x0E, par end
- return 0, 0
- elseif L == 20 then
- if par >= 0x20 then return 0x31, par end
- if isIT then return 0x32, par end
- return 0, 0
- elseif L == 21 then return 0x34, par
- elseif L == 22 then
- if isIT then return 0x10, min(64, floor(par / 2)) end
- return 0x10, min(64, par)
- elseif L == 23 and isIT then return 0x11, par
- end
- return 0, 0
- end
- local function loadMOD()
- local sg = { fmt = "MOD", pm = 1, minPer = 113, maxPer = 856, slideMul = 1, mem = false,
- insMode = false, vibScale = 2, gvolScale = 1, gvol = 64, speed = 6, tempo = 125,
- samples = {}, patterns = {}, order = {}, mute = {}, restart = 1, holdTone = true }
- local pos = 20
- for i = 1, 31 do
- local len = u16be(pos + 22) * 2
- local fine = band(u8(pos + 24), 15)
- if fine > 7 then fine = fine - 16 end
- local ls, ll = u16be(pos + 26) * 2, u16be(pos + 28) * 2
- sg.samples[i] = { len = len, vol = min(64, u8(pos + 25)), loopStart = ls, loopLen = ll,
- loop = ll > 2 and ls < len, fine = fine, c4spd = 8363, ref = 49, tune = 0, gvol = 1 }
- if ls + ll > len then sg.samples[i].loopLen = max(0, len - ls) end
- pos = pos + 30
- end
- local songLength = min(128, max(1, u8(950)))
- local rst = u8(951)
- sg.songLength = songLength
- sg.restart = (rst < songLength) and rst + 1 or 1
- local maxPattern = 0
- for i = 1, 128 do
- local v = u8(951 + i)
- sg.order[i] = v
- if v > maxPattern then maxPattern = v end
- end
- local tag = D:sub(1081, 1084)
- local ch = 4
- if tag == "M.K." or tag == "M!K!" or tag == "FLT4" or tag == "4CHN" then ch = 4
- elseif tag == "6CHN" then ch = 6
- elseif tag == "8CHN" or tag == "OKTA" or tag == "CD81" or tag == "FLT8" then ch = 8
- else
- local n = tag:match("^(%d%d)CH") or tag:match("^(%d)CHN")
- if n then ch = tonumber(n) end
- end
- sg.channels = ch
- pos = 1084
- for p = 0, maxPattern do
- local pat = { rows = 64 }
- for row = 1, 64 do
- local r = {}
- for c = 1, ch do
- local b1, b2, b3, b4 = u8(pos), u8(pos + 1), u8(pos + 2), u8(pos + 3)
- pos = pos + 4
- if b1 ~= 0 or b2 ~= 0 or b3 ~= 0 or b4 ~= 0 then
- r[c] = { n = bor(lshift(band(b1, 15), 8), b2), i = bor(band(b1, 0xF0), rshift(b3, 4)),
- vc = 0, vv = 0, e = band(b3, 15), p = b4 }
- end
- end
- pat[row] = r
- end
- sg.patterns[p] = pat
- end
- for i = 1, 31 do
- local s = sg.samples[i]
- s.data = readS8(pos, s.len)
- pos = pos + s.len
- end
- return sg
- end
- local function loadS3M()
- local ordNum, insNum, patNum = u16(32), u16(34), u16(36)
- local sflags, cwt, ffi = u16(38), u16(40), u16(42)
- local sg = { fmt = "S3M", pm = 2, minPer = 1, maxPer = 32767, slideMul = 4, mem = true,
- insMode = false, vibScale = 8, gvolScale = 1, samples = {}, patterns = {}, order = {},
- mute = {}, restart = 1, songLength = max(1, ordNum), letters = true, markers = true,
- shareG = true, holdTone = false, nnaOn = false,
- fastVol = band(sflags, 64) ~= 0 or cwt == 0x1300 }
- sg.gvol = u8(48)
- if sg.gvol == 0 or sg.gvol > 64 then sg.gvol = 64 end
- sg.speed = u8(49); if sg.speed == 0 then sg.speed = 6 end
- sg.tempo = u8(50); if sg.tempo < 32 then sg.tempo = 125 end
- local nch = 0
- for c = 0, 31 do
- local cs = u8(64 + c)
- if cs ~= 255 and band(cs, 0x7F) < 16 then
- nch = c + 1
- if cs >= 128 then sg.mute[c + 1] = true end
- end
- end
- if nch == 0 then nch = 1 end
- sg.channels = nch
- for i = 1, ordNum do sg.order[i] = u8(96 + i - 1) end
- local insBase = 96 + ordNum
- local patBase = insBase + insNum * 2
- for i = 1, insNum do
- local ptr = u16(insBase + (i - 1) * 2) * 16
- local s = { len = 0, vol = 0, loopStart = 0, loopLen = 0, loop = false, fine = 0,
- c4spd = 8363, ref = 49, tune = 0, gvol = 1, data = {} }
- if ptr > 0 and u8(ptr) == 1 then
- local dataOff = (u8(ptr + 13) * 65536 + u16(ptr + 14)) * 16
- local len, ls, le = u32(ptr + 16), u32(ptr + 20), u32(ptr + 24)
- local flg = u8(ptr + 31)
- local is16 = band(flg, 4) ~= 0
- local bps = is16 and 2 or 1
- if dataOff + len * bps > #D then len = max(0, floor((#D - dataOff) / bps)) end
- s.vol = min(64, u8(ptr + 28))
- s.c4spd = u32(ptr + 32)
- if s.c4spd == 0 then s.c4spd = 8363 end
- local data = {}
- if is16 then
- for j = 1, len do
- local v = u16(dataOff + (j - 1) * 2)
- if ffi == 1 then v = v > 32767 and v - 65536 or v else v = v - 32768 end
- data[j] = v / 256
- end
- else
- for j = 1, len do
- local b = u8(dataOff + j - 1)
- if ffi == 1 then data[j] = b > 127 and b - 256 or b else data[j] = b - 128 end
- end
- end
- s.data, s.len = data, len
- if band(flg, 1) ~= 0 and le > ls and ls < len then
- s.loop, s.loopStart, s.loopLen = true, ls, min(le, len) - ls
- end
- end
- sg.samples[i] = s
- end
- for p = 0, patNum - 1 do
- local ptr = u16(patBase + p * 2) * 16
- local pat = { rows = 64 }
- for r = 1, 64 do pat[r] = {} end
- if ptr > 0 then
- local pos = ptr + 2
- for r = 1, 64 do
- local row = pat[r]
- while true do
- local what = u8(pos)
- pos = pos + 1
- if what == 0 then break end
- local cell = { n = 0, i = 0, vc = 0, vv = 0, e = 0, p = 0 }
- if band(what, 32) ~= 0 then
- local note = u8(pos)
- cell.i = u8(pos + 1)
- pos = pos + 2
- if note == 254 then cell.n = NOTE_CUT
- elseif note ~= 255 then cell.n = rshift(note, 4) * 12 + band(note, 15) + 1 end
- end
- if band(what, 64) ~= 0 then
- cell.vc, cell.vv = 1, min(64, u8(pos))
- pos = pos + 1
- end
- if band(what, 128) ~= 0 then
- local cmd, par = u8(pos), u8(pos + 1)
- pos = pos + 2
- if cmd > 0 and cmd < 27 then cell.e, cell.p = LE + cmd, par end
- end
- local c = band(what, 31) + 1
- if c <= nch then row[c] = cell end
- end
- end
- end
- sg.patterns[p] = pat
- end
- return sg
- end
- local function xmVol(v)
- if v < 0x10 then return 0, 0 end
- if v <= 0x50 then return 1, v - 0x10 end
- local hi, lo = rshift(v, 4), band(v, 15)
- if hi == 6 then return 2, lo
- elseif hi == 7 then return 3, lo
- elseif hi == 8 then return 4, lo
- elseif hi == 9 then return 5, lo
- elseif hi == 0xA then return 6, lo
- elseif hi == 0xB then return 7, lo
- elseif hi == 0xF then return 11, lo * 16 end
- return 0, 0
- end
- local function loadXM()
- local hdr = u32(60)
- local songLen, rst, nch, npat, nins = u16(64), u16(66), u16(68), u16(70), u16(72)
- local flags = u16(74)
- local sg = { fmt = "XM", pm = 3, minPer = 0, maxPer = 9600, slideMul = 4, mem = true,
- insMode = true, vibScale = 8, gvolScale = 1, gvol = 64, samples = {}, patterns = {},
- order = {}, mute = {}, instruments = {}, channels = nch, holdTone = true,
- songLength = max(1, min(256, songLen)), linear = band(flags, 1) ~= 0 }
- sg.restart = (rst < sg.songLength) and rst + 1 or 1
- if not sg.linear then sg.pm, sg.minPer, sg.maxPer = 2, 1, 32767 end
- sg.speed = u16(76); if sg.speed == 0 then sg.speed = 6 end
- sg.tempo = u16(78); if sg.tempo < 32 then sg.tempo = 125 end
- for i = 1, 256 do sg.order[i] = u8(80 + i - 1) end
- local pos = 60 + hdr
- for p = 0, npat - 1 do
- local hl = u32(pos)
- local rows = u16(pos + 5)
- local psize = u16(pos + 7)
- local dp = pos + hl
- local pat = { rows = rows }
- for r = 1, rows do pat[r] = {} end
- if psize > 0 then
- for r = 1, rows do
- local row = pat[r]
- for c = 1, nch do
- local b = u8(dp)
- dp = dp + 1
- local note, ins, vol, eff, par = 0, 0, 0, 0, 0
- if b >= 128 then
- if band(b, 1) ~= 0 then note = u8(dp); dp = dp + 1 end
- if band(b, 2) ~= 0 then ins = u8(dp); dp = dp + 1 end
- if band(b, 4) ~= 0 then vol = u8(dp); dp = dp + 1 end
- if band(b, 8) ~= 0 then eff = u8(dp); dp = dp + 1 end
- if band(b, 16) ~= 0 then par = u8(dp); dp = dp + 1 end
- else
- note, ins, vol, eff, par = b, u8(dp), u8(dp + 1), u8(dp + 2), u8(dp + 3)
- dp = dp + 4
- end
- if note ~= 0 or ins ~= 0 or vol ~= 0 or eff ~= 0 or par ~= 0 then
- local vc, vv = xmVol(vol)
- if note == 97 then note = NOTE_OFF end
- if eff >= LE then eff, par = 0, 0 end
- row[c] = { n = note, i = ins, vc = vc, vv = vv, e = eff, p = par }
- end
- end
- end
- end
- sg.patterns[p] = pat
- pos = pos + hl + psize
- end
- for i = 1, nins do
- local p = pos
- local hs = max(29, u32(p))
- local ns = u16(p + 27)
- local inst = { samples = {}, keymap = {}, gvol = 1, fadeDec = 0 }
- if ns > 0 then
- local shs = u32(p + 29)
- if shs < 18 then shs = 40 end
- for n = 1, 96 do inst.keymap[n] = u8(p + 33 + n - 1) + 1 end
- local nv = min(12, u8(p + 225))
- local pts = {}
- for k = 0, nv - 1 do
- pts[#pts + 1] = { u16(p + 129 + k * 4), min(64, u16(p + 131 + k * 4)) }
- end
- local vt = u8(p + 233)
- inst.venv = makeEnv(pts, band(vt, 1) ~= 0, band(vt, 2) ~= 0,
- u8(p + 227) + 1, u8(p + 227) + 1, band(vt, 4) ~= 0, u8(p + 228) + 1, u8(p + 229) + 1)
- inst.fadeDec = u16(p + 239) * 2
- local vdepth = band(u8(p + 237), 15)
- if vdepth > 0 then
- inst.av = { type = u8(p + 235), sweep = u8(p + 236), depth = vdepth,
- rate = band(u8(p + 238), 63) }
- end
- local sp = p + hs
- local dpos = sp + ns * shs
- for k = 1, ns do
- local h = sp + (k - 1) * shs
- local len, ls, ll = u32(h), u32(h + 4), u32(h + 8)
- local typ = band(u8(h + 14), 0xFF)
- local is16 = band(typ, 16) ~= 0
- local rawLen = len
- if is16 then len, ls, ll = floor(len / 2), floor(ls / 2), floor(ll / 2) end
- local s = { len = len, vol = min(64, u8(h + 12)), loopStart = 0, loopLen = 0,
- loop = false, fine = 0, c4spd = 8363, ref = 49, gvol = 1,
- tune = s8(h + 16) + s8(h + 13) / 128 }
- local data, acc = {}, 0
- if is16 then
- for j = 1, len do
- acc = acc + s16(dpos + (j - 1) * 2)
- if acc > 32767 then acc = acc - 65536 elseif acc < -32768 then acc = acc + 65536 end
- data[j] = acc / 256
- end
- else
- for j = 1, len do
- acc = acc + s8(dpos + j - 1)
- if acc > 127 then acc = acc - 256 elseif acc < -128 then acc = acc + 256 end
- data[j] = acc
- end
- end
- s.data = data
- local lt = band(typ, 3)
- if lt ~= 0 and ll > 0 and ls < len then
- s.loop, s.loopStart, s.loopLen = true, ls, min(ll, len - ls)
- if lt == 2 then unrollPingPong(s, "loop") end
- end
- inst.samples[k] = s
- dpos = dpos + rawLen
- end
- pos = dpos
- else
- pos = p + hs
- end
- sg.instruments[i] = inst
- end
- return sg
- end
- local ITTONEP = { 1, 4, 8, 16, 32, 64, 96, 128, 255 }
- local ITVIBTYPE = { [0] = 0, [1] = 2, [2] = 1, [3] = 0 }
- local function itVol(v)
- if v <= 64 then return 1, v
- elseif v <= 74 then return 5, v - 65
- elseif v <= 84 then return 4, v - 75
- elseif v <= 94 then return 3, v - 85
- elseif v <= 104 then return 2, v - 95
- elseif v <= 114 then return 13, (v - 105) * 4
- elseif v <= 124 then return 12, (v - 115) * 4
- elseif v >= 193 and v <= 202 then return 11, ITTONEP[min(9, max(1, v - 193))] or 1
- elseif v >= 203 and v <= 212 then return 7, v - 203 end
- return 0, 0
- end
- local function loadIT()
- local ordNum, insNum, smpNum, patNum = u16(32), u16(34), u16(36), u16(38)
- local flags = u16(44)
- local sg = { fmt = "IT", pm = 3, minPer = 0, maxPer = 9600, slideMul = 4, mem = true,
- insMode = band(flags, 4) ~= 0, vibScale = 6, gvolScale = 0.5, samples = {}, patterns = {},
- order = {}, mute = {}, instruments = {}, restart = 1, songLength = max(1, ordNum),
- linear = band(flags, 8) ~= 0, letters = true, markers = true, holdTone = false,
- shareG = band(flags, 32) ~= 0, chnvol = {} }
- sg.nnaOn = sg.insMode
- sg.gvol = min(64, floor(u8(48) / 2))
- if not sg.linear then sg.pm, sg.minPer, sg.maxPer = 2, 1, 32767 end
- if sg.gvol == 0 then sg.gvol = 64 end
- sg.speed = u8(50); if sg.speed == 0 then sg.speed = 6 end
- sg.tempo = u8(51); if sg.tempo < 32 then sg.tempo = 125 end
- for c = 0, 63 do
- if u8(64 + c) >= 128 then sg.mute[c + 1] = true end
- sg.chnvol[c + 1] = min(64, u8(128 + c))
- end
- for i = 1, ordNum do sg.order[i] = u8(192 + i - 1) end
- local insBase = 192 + ordNum
- local smpBase = insBase + insNum * 4
- local patBase = smpBase + smpNum * 4
- for i = 1, smpNum do
- local p = u32(smpBase + (i - 1) * 4)
- local s = { len = 0, vol = 0, loopStart = 0, loopLen = 0, loop = false, fine = 0,
- c4spd = 8363, ref = 61, tune = 0, gvol = 1, data = {} }
- if p > 0 and D:sub(p + 1, p + 4) == "IMPS" then
- local flg, cvt = u8(p + 18), u8(p + 46)
- local len = u32(p + 48)
- local ls, le = u32(p + 52), u32(p + 56)
- local sls, sle = u32(p + 64), u32(p + 68)
- local dp = u32(p + 72)
- local is16 = band(flg, 2) ~= 0
- local signed = band(cvt, 1) ~= 0
- s.gvol = min(64, u8(p + 17)) / 64
- s.vol = min(64, u8(p + 19))
- s.c4spd = u32(p + 60)
- if s.c4spd == 0 then s.c4spd = 8363 end
- local vdepth = u8(p + 77)
- if vdepth > 0 then
- s.av = { type = ITVIBTYPE[u8(p + 79)] or 0, sweep = u8(p + 78), depth = min(64, vdepth),
- rate = u8(p + 76) }
- end
- if band(flg, 1) ~= 0 and len > 0 and dp > 0 then
- local bps = is16 and 2 or 1
- if band(flg, 8) == 0 and dp + len * bps > #D then
- len = max(0, floor((#D - dp) / bps))
- end
- local data
- if band(flg, 8) ~= 0 then
- local ok, res = pcall(itDecompress, dp, len, is16, band(cvt, 4) ~= 0)
- if ok then data = res else data = {}; for j = 1, len do data[j] = 0 end end
- else
- data = {}
- local acc = 0
- local delta = band(cvt, 4) ~= 0
- for j = 1, len do
- local v
- if is16 then
- v = u16(dp + (j - 1) * 2)
- if signed then v = v > 32767 and v - 65536 or v else v = v - 32768 end
- if delta then
- acc = acc + v
- if acc > 32767 then acc = acc - 65536 elseif acc < -32768 then acc = acc + 65536 end
- v = acc
- end
- v = v / 256
- else
- v = u8(dp + j - 1)
- if signed then v = v > 127 and v - 256 or v else v = v - 128 end
- if delta then
- acc = acc + v
- if acc > 127 then acc = acc - 256 elseif acc < -128 then acc = acc + 256 end
- v = acc
- end
- end
- data[j] = v
- end
- end
- s.data, s.len = data, len
- local pingN, pingS = false, false
- if band(flg, 16) ~= 0 and le > ls and ls < len then
- s.loop, s.loopStart, s.loopLen = true, ls, min(le, len) - ls
- pingN = band(flg, 64) ~= 0
- end
- if band(flg, 32) ~= 0 and sle > sls and sls < len then
- s.sus, s.susStart, s.susLen = true, sls, min(sle, len) - sls
- pingS = band(flg, 128) ~= 0
- end
- if s.loop and pingN then unrollPingPong(s, "loop") end
- if s.sus and pingS and not s.loop then unrollPingPong(s, "sus") end
- end
- end
- sg.samples[i] = s
- end
- if sg.insMode then
- for i = 1, insNum do
- local p = u32(insBase + (i - 1) * 4)
- if p > 0 and D:sub(p + 1, p + 4) == "IMPI" then
- local inst = { notemap = {}, gvol = min(128, u8(p + 24)) / 128,
- fadeDec = u16(p + 20) * 64,
- nna = min(3, u8(p + 17)), dct = min(3, u8(p + 18)), dca = min(2, u8(p + 19)) }
- for n = 0, 119 do
- inst.notemap[n + 1] = { u8(p + 64 + n * 2) + 1, u8(p + 65 + n * 2) }
- end
- local ef, num = u8(p + 304), min(25, u8(p + 305))
- local pts = {}
- for k = 0, num - 1 do
- pts[#pts + 1] = { u16(p + 311 + k * 3), min(64, u8(p + 310 + k * 3)) }
- end
- inst.venv = makeEnv(pts, band(ef, 1) ~= 0, band(ef, 4) ~= 0,
- u8(p + 308) + 1, u8(p + 309) + 1, band(ef, 2) ~= 0, u8(p + 306) + 1, u8(p + 307) + 1)
- local pf = u8(p + 468)
- if band(pf, 1) ~= 0 and band(pf, 128) == 0 then
- local pn = min(25, u8(p + 469))
- local ppts = {}
- for k = 0, pn - 1 do
- ppts[#ppts + 1] = { u16(p + 475 + k * 3), s8(p + 474 + k * 3) }
- end
- inst.penv = makeEnv(ppts, true, band(pf, 4) ~= 0,
- u8(p + 472) + 1, u8(p + 473) + 1, band(pf, 2) ~= 0, u8(p + 470) + 1, u8(p + 471) + 1)
- end
- sg.instruments[i] = inst
- end
- end
- end
- local maxCh = 1
- for p = 0, patNum - 1 do
- local ptr = u32(patBase + p * 4)
- local rows = 64
- local pat = {}
- if ptr > 0 then
- rows = u16(ptr + 2)
- if rows < 1 then rows = 64 end
- for r = 1, rows do pat[r] = {} end
- local pos = ptr + 8
- local lmask, lnote, lins, lvol, lcmd, lpar = {}, {}, {}, {}, {}, {}
- for r = 1, rows do
- local row = pat[r]
- while true do
- local cv = u8(pos)
- pos = pos + 1
- if cv == 0 then break end
- local ch = band(cv - 1, 63)
- local mask
- if band(cv, 128) ~= 0 then
- mask = u8(pos)
- pos = pos + 1
- lmask[ch] = mask
- else
- mask = lmask[ch] or 0
- end
- local note, ins, vol, cmd, par
- if band(mask, 1) ~= 0 then note = u8(pos); pos = pos + 1; lnote[ch] = note
- elseif band(mask, 16) ~= 0 then note = lnote[ch] end
- if band(mask, 2) ~= 0 then ins = u8(pos); pos = pos + 1; lins[ch] = ins
- elseif band(mask, 32) ~= 0 then ins = lins[ch] end
- if band(mask, 4) ~= 0 then vol = u8(pos); pos = pos + 1; lvol[ch] = vol
- elseif band(mask, 64) ~= 0 then vol = lvol[ch] end
- if band(mask, 8) ~= 0 then
- cmd, par = u8(pos), u8(pos + 1)
- pos = pos + 2
- lcmd[ch], lpar[ch] = cmd, par
- elseif band(mask, 128) ~= 0 then cmd, par = lcmd[ch], lpar[ch] end
- local cell = { n = 0, i = ins or 0, vc = 0, vv = 0, e = 0, p = 0 }
- if note then
- if note < 120 then cell.n = note + 1
- elseif note == 254 then cell.n = NOTE_CUT
- elseif note == 255 then cell.n = NOTE_OFF
- else cell.n = NOTE_FADE end
- end
- if vol then cell.vc, cell.vv = itVol(vol) end
- if cmd and cmd > 0 and cmd < 27 then cell.e, cell.p = LE + cmd, par or 0 end
- row[ch + 1] = cell
- if ch + 1 > maxCh then maxCh = ch + 1 end
- end
- end
- else
- for r = 1, rows do pat[r] = {} end
- end
- pat.rows = rows
- sg.patterns[p] = pat
- end
- sg.channels = maxCh
- return sg
- end
- local tracks = {}
- local bg = {}
- local speed, bpm = 6, 125
- local pendingOrder, pendingBreak, loopTarget, rowRepeat, fineDelay = nil, nil, nil, 0, 0
- local rowNow = 1
- local MEMEFF = { [1] = true, [2] = true, [5] = true, [6] = true, [7] = true, [9] = true,
- [10] = true, [0x11] = true, [0x1B] = true, [0x1D] = true }
- local function newTrack(c)
- return { sample = nil, inst = nil, instNum = 0, cursor = 0, per = 0, tper = 0, step = 0, baseF = 0,
- volume = 64, volumeScale = 0, cvol = (song.chnvol and song.chnvol[c]) or 64, active = false,
- effect = 0, eData = 0, vc = 0, vv = 0,
- portSpeed = 0, vibSpeed = 0, vibDepth = 0, vibPos = 0, tremSpeed = 0, tremDepth = 0, tremPos = 0,
- vibOff = 0, tremOff = 0, arp = 0, mem = {}, keyoff = false, notefade = false,
- envPos = 0, penvPos = 0, fade = 65536, venvOff = false, penvOff = false,
- loopRow = 1, loopCnt = 0, tremorPos = 0, tremorOff = false,
- vibWave = 0, tremWave = 0, vibFine = false, fineOv = nil, fineSemi = 0, curScale = 0, ghost = nil,
- av = nil, avPos = 0, avAmp = 0, avOff = 0, hiOff = 0, nna = 0,
- vNote = 0, vSmp = 0, vIns = 0, muted = song.mute[c] }
- end
- local function slideUp(t, amt)
- if song.pm == 3 then t.per = min(t.per + amt, song.maxPer)
- else t.per = max(t.per - amt, song.minPer) end
- end
- local function slideDown(t, amt)
- if song.pm == 3 then t.per = max(t.per - amt, 0)
- else t.per = min(t.per + amt, song.maxPer) end
- end
- local function tonePorta(t, spd)
- local amt = spd * song.slideMul
- if t.per < t.tper then t.per = min(t.tper, t.per + amt)
- elseif t.per > t.tper then t.per = max(t.tper, t.per - amt) end
- end
- local function notePitch(smp, note)
- if song.pm == 1 then return note end
- if song.pm == 2 then return 14317456 / (smp.c4spd * 2 ^ ((note - smp.ref + smp.tune) / 12)) end
- return (note - 1 + smp.tune) * 64
- end
- local function pickSample(num, note)
- if song.insMode then
- local inst = song.instruments[num]
- if not inst then return nil end
- if song.fmt == "XM" then
- local k = inst.keymap[note] or 1
- return inst.samples[k], note, inst, k
- end
- local nm = inst.notemap[note]
- if not nm then return nil end
- return song.samples[nm[2]], nm[1], inst, nm[2]
- end
- return song.samples[num], note, nil, num
- end
- local function getLoop(s, keyoff)
- if s.sus and not keyoff and s.susLen > 0 then return true, s.susStart, s.susLen end
- if s.loop and s.loopLen > 0 then return true, s.loopStart, s.loopLen end
- return false, 0, 0
- end
- local function makeGhost(t)
- local s = t.sample
- if t.active and s and s.len > 0 and t.curScale > 0 then
- local hasLoop, ls, ll = getLoop(s, t.keyoff)
- t.ghost = { data = s.data, len = s.len, cur = t.cursor, step = t.step, vol = t.curScale,
- hasLoop = hasLoop, ls = ls, le = ls + ll, ll = ll }
- end
- end
- local function killVoice(v)
- makeGhost(v)
- v.active = false
- end
- local function applyKeyOff(v)
- v.keyoff = true
- local inst = v.inst
- if not (inst and inst.venv) then
- if song.fmt == "IT" and inst then
- if inst.fadeDec > 0 then v.notefade = true else v.fade = 0 end
- else
- v.volume = 0
- end
- end
- end
- local function noteCut(t)
- if song.fmt == "IT" then killVoice(t) else t.volume = 0 end
- end
- local function spawnBG(t, c)
- if t.muted then return nil end
- local v = { chan = c, sample = t.sample, inst = t.inst, cursor = t.cursor, step = t.step,
- baseF = t.baseF, curScale = t.curScale, volumeScale = t.volumeScale, volume = t.volume,
- cvol = t.cvol, envPos = t.envPos, penvPos = t.penvPos, fade = t.fade, keyoff = t.keyoff,
- notefade = t.notefade, active = true, tremOff = 0, tremorOff = false, venvOff = t.venvOff,
- penvOff = t.penvOff, av = t.av, avPos = t.avPos, avAmp = t.avAmp, avOff = t.avOff,
- note = t.vNote, smpNum = t.vSmp, insNum = t.vIns }
- if #bg >= MAX_BG_VOICES then
- for i = 1, #bg do
- if bg[i].active then killVoice(bg[i]) break end
- end
- end
- bg[#bg + 1] = v
- return v
- end
- local function dnaCheck(c, inst, insNum, smpNum, note)
- local dct, dca = inst.dct, inst.dca
- for i = 1, #bg do
- local v = bg[i]
- if v.active and v.chan == c and v.insNum == insNum then
- local dup = (dct == 1 and v.note == note) or (dct == 2 and v.smpNum == smpNum) or dct == 3
- if dup then
- if dca == 0 then killVoice(v)
- elseif dca == 1 then applyKeyOff(v)
- else v.notefade = true end
- end
- end
- end
- end
- local function startCell(t, cell, c, cmd)
- local n, ins = cell.n, cell.i
- local isTone = (cmd == 3 or cmd == 5 or cmd == 0x3A or cell.vc == 11)
- if ins > 0 then t.instNum = ins end
- if n > 0 then
- local smp, nn, inst, smpNum = pickSample(t.instNum, n)
- if smp and smp.len > 0 then
- if isTone and t.sample and (t.active or song.holdTone) then
- t.tper = notePitch(smp, nn)
- if ins > 0 then
- t.volume = t.sample.vol
- if song.insMode then
- t.keyoff, t.fade, t.envPos, t.penvPos, t.notefade = false, 65536, 0, 0, false
- end
- end
- else
- if song.nnaOn and t.active and t.sample and t.curScale > 0 and t.nna ~= 0 then
- local v = spawnBG(t, c)
- if v then
- if t.nna == 2 then applyKeyOff(v)
- elseif t.nna == 3 then v.notefade = true end
- end
- else
- makeGhost(t)
- end
- if song.nnaOn and inst and inst.dct and inst.dct ~= 0 then
- dnaCheck(c, inst, t.instNum, smpNum, nn)
- end
- t.sample, t.inst = smp, inst
- t.per = notePitch(smp, nn)
- t.tper = t.per
- t.cursor = 0
- t.active = true
- t.curScale = 0
- t.fineOv = nil
- t.fineSemi = 0
- if band(t.vibWave, 4) == 0 then t.vibPos = 0 end
- if band(t.tremWave, 4) == 0 then t.tremPos = 0 end
- t.tremorPos = 0
- t.keyoff, t.notefade, t.fade, t.envPos, t.penvPos = false, false, 65536, 0, 0
- t.venvOff, t.penvOff = false, false
- t.nna = inst and inst.nna or 0
- t.vNote, t.vSmp, t.vIns = nn, smpNum, t.instNum
- t.av = smp.av or (inst and inst.av) or nil
- t.avPos, t.avAmp, t.avOff = 0, 0, 0
- if ins > 0 then t.volume = smp.vol end
- end
- end
- elseif n == NOTE_OFF then
- applyKeyOff(t)
- elseif n == NOTE_FADE then
- t.notefade = true
- elseif n == NOTE_CUT then
- killVoice(t)
- elseif ins > 0 then
- if song.insMode then
- local inst = song.instruments[ins]
- if inst then t.inst = inst end
- if t.sample then t.volume = t.sample.vol end
- t.keyoff, t.notefade, t.fade, t.envPos, t.penvPos = false, false, 65536, 0, 0
- else
- local smp = song.samples[ins]
- if smp and smp.len > 0 then t.volume = smp.vol end
- end
- end
- end
- local function voiceScale(t)
- local s = t.sample
- local v = t.volume + t.tremOff
- if v < 0 then v = 0 elseif v > 64 then v = 64 end
- local scale = v / 64 * (t.cvol / 64) * song.gvol / 64 * s.gvol
- local inst = t.inst
- if inst then
- if inst.venv and not t.venvOff then scale = scale * envValue(inst.venv, t.envPos) / 64 end
- scale = scale * t.fade / 65536 * inst.gvol
- end
- if t.tremorOff then scale = 0 end
- return scale
- end
- local function applyPitchMods(t, f)
- local semis = t.avOff
- local inst = t.inst
- if inst and inst.penv and not t.penvOff then
- semis = semis + envValue(inst.penv, t.penvPos) / 2
- end
- if semis ~= 0 then f = f * 2 ^ (semis / 12) end
- if f > 131072 then f = 131072 end
- return f / MIX_RATE
- end
- local function finalizeChannel(t)
- local s = t.sample
- if not s then
- t.step, t.volumeScale = 0, 0
- return
- end
- t.volumeScale = voiceScale(t)
- local pm = song.pm
- if pm ~= 3 and t.per <= 0 then
- t.step, t.volumeScale = 0, 0
- return
- end
- local per = t.per + t.vibOff
- local f
- if pm == 1 then
- if per < 1 then per = 1 end
- f = AMIGA_CLOCK / per
- local fine = t.fineOv or s.fine
- if fine ~= 0 then f = f * 2 ^ (fine / 96) end
- if t.arp ~= 0 then f = f * 2 ^ (t.arp / 12) end
- elseif pm == 2 then
- if per < 1 then per = 1 end
- f = 14317456 / per
- if t.arp ~= 0 then f = f * 2 ^ (t.arp / 12) end
- else
- f = s.c4spd * 2 ^ ((per / 64 + t.arp + t.fineSemi - (s.ref - 1)) / 12)
- end
- t.baseF = f
- t.step = applyPitchMods(t, f)
- end
- local function avWave(kind, pos)
- if kind == 1 then return pos < 128 and 1 or -1
- elseif kind == 2 then return (((64 - floor((pos + 1) / 2)) % 128) - 64) / 64
- elseif kind == 3 then return (((64 + floor((pos + 1) / 2)) % 128) - 64) / 64 end
- return sin(pos * 0.02454369)
- end
- local function advanceChannel(t)
- if not t.active then return end
- local inst = t.inst
- if inst then
- if inst.venv and not t.venvOff then advanceEnv(inst.venv, t, "envPos") end
- if inst.penv then advanceEnv(inst.penv, t, "penvPos") end
- if t.keyoff or t.notefade then
- if inst.fadeDec > 0 and t.fade > 0 then t.fade = max(0, t.fade - inst.fadeDec) end
- if t.fade <= 0 then
- t.fade = 0
- killVoice(t)
- return
- end
- end
- end
- local av = t.av
- if av then
- local full = av.depth * 256
- if av.sweep == 0 then
- t.avAmp = full
- elseif song.fmt == "IT" then
- t.avAmp = min(full, t.avAmp + av.sweep * 2)
- elseif not t.keyoff then
- t.avAmp = min(full, t.avAmp + full / av.sweep)
- end
- t.avOff = avWave(av.type, t.avPos) * (t.avAmp / 256) / 64
- t.avPos = (t.avPos + av.rate) % 256
- end
- end
- local function updateBG(v)
- if not v.active then return end
- v.volumeScale = voiceScale(v)
- v.step = applyPitchMods(v, v.baseF)
- advanceChannel(v)
- if v.active and (v.keyoff or v.notefade) and v.volumeScale <= 0.00001 then killVoice(v) end
- end
- local function retrigVol(v, x)
- if x == 1 then v = v - 1 elseif x == 2 then v = v - 2 elseif x == 3 then v = v - 4
- elseif x == 4 then v = v - 8 elseif x == 5 then v = v - 16
- elseif x == 6 then v = floor(v * 2 / 3) elseif x == 7 then v = floor(v / 2)
- elseif x == 9 then v = v + 1 elseif x == 10 then v = v + 2 elseif x == 11 then v = v + 4
- elseif x == 12 then v = v + 8 elseif x == 13 then v = v + 16
- elseif x == 14 then v = floor(v * 3 / 2) elseif x == 15 then v = v * 2 end
- return max(0, min(64, v))
- end
- local function waveform(kind, pos)
- local p = pos % 64
- if kind == 1 then
- local m = (p % 32) * 8
- if p < 32 then return m / 255 end
- return -(255 - m) / 255
- elseif kind == 2 then
- return p < 32 and 1 or -1
- end
- return sin(p * 0.09817477)
- end
- local function doVibrato(t)
- local d = t.vibDepth * song.vibScale
- if t.vibFine then d = d * 0.25 end
- t.vibOff = waveform(band(t.vibWave, 3), t.vibPos) * d
- t.vibPos = (t.vibPos + t.vibSpeed) % 64
- end
- local function slideAmount(data)
- local hi = rshift(data, 4)
- if hi > 0 then return hi end
- return -band(data, 15)
- end
- local function dSlide(par)
- local hi, lo = rshift(par, 4), band(par, 15)
- if lo == 0 then return 0, hi
- elseif hi == 0 then return 0, -lo
- elseif lo == 15 then return hi, 0
- elseif hi == 15 then return -lo, 0 end
- return 0, 0
- end
- local function clampVol(v)
- if v < 0 then return 0 elseif v > 64 then return 64 end
- return v
- end
- local function resolveLetter(t, L, par)
- local m = t.mem
- if L == 4 or L == 11 or L == 12 then
- if par == 0 then par = m.vs or 0 else m.vs = par end
- elseif L == 5 or L == 6 then
- if par == 0 then par = m.pu or 0 else m.pu = par end
- elseif L == 7 then
- local key = song.shareG and "pu" or "g"
- if par == 0 then par = m[key] or 0 else m[key] = par end
- elseif L == 9 then
- if par == 0 then par = m.i or 0 else m.i = par end
- elseif L == 10 then
- if par == 0 then par = m.j or 0 else m.j = par end
- elseif L == 14 then
- if par == 0 then par = m.n or 0 else m.n = par end
- elseif L == 15 then
- if par == 0 then par = m.o or 0 else m.o = par end
- elseif L == 17 then
- if par == 0 then par = m.q or 0 else m.q = par end
- elseif L == 19 then
- if par == 0 then par = m.s or 0 else m.s = par end
- elseif L == 20 then
- if par < 0x20 then
- if par == 0 then par = m.t or 0 else m.t = par end
- end
- elseif L == 23 then
- if par == 0 then par = m.w or 0 else m.w = par end
- end
- return convertLetter(L, par)
- end
- local function doS7(t, c, p)
- if p <= 2 then
- for i = 1, #bg do
- local v = bg[i]
- if v.active and v.chan == c then
- if p == 0 then killVoice(v)
- elseif p == 1 then applyKeyOff(v)
- else v.notefade = true end
- end
- end
- elseif p >= 3 and p <= 6 then t.nna = p - 3
- elseif p == 7 then t.venvOff = true
- elseif p == 8 then t.venvOff = false
- elseif p == 0xB then t.penvOff = true
- elseif p == 0xC then t.penvOff = false
- end
- end
- local function processChannelTick(t, tick, c)
- local cmd, data, vc, vv = t.effect, t.eData, t.vc, t.vv
- t.vibOff, t.tremOff, t.arp, t.tremorOff = 0, 0, 0, false
- if t.delayTick and t.delayTick == tick then
- local cl = t.delayCell
- t.delayTick, t.delayCell = nil, nil
- startCell(t, cl, c, 0x0E)
- if cl.vc == 1 then t.volume = cl.vv end
- end
- local sub, sd = rshift(data, 4), band(data, 15)
- if tick == 0 then
- if vc == 1 then t.volume = vv
- elseif vc == 4 then t.volume = max(0, t.volume - vv)
- elseif vc == 5 then t.volume = min(64, t.volume + vv)
- elseif vc == 6 then if vv > 0 then t.vibSpeed = vv end
- elseif vc == 7 then if vv > 0 then t.vibDepth = vv end
- elseif vc == 11 then if vv > 0 then t.portSpeed = vv end
- end
- if cmd == 0xC then
- t.volume = min(data, 64)
- elseif cmd == 0xF then
- if data > 0 and data < 32 then speed = data
- elseif data >= 32 then bpm = max(32, min(255, data)) end
- elseif cmd == 0x30 then
- if data > 0 then speed = data end
- elseif cmd == 0x31 then
- bpm = max(32, min(255, data))
- elseif cmd == 0xD then
- pendingBreak = floor(data / 16) * 10 + (data % 16) + 1
- elseif cmd == 0x3B then
- pendingBreak = data + 1
- elseif cmd == 0xB then
- pendingOrder = data + 1
- elseif cmd == 0x3 or cmd == 0x5 then
- if cmd == 0x3 and data > 0 then t.portSpeed = data end
- elseif cmd == 0x4 or cmd == 0x6 or cmd == 0x34 then
- if cmd ~= 0x6 and data > 0 then
- t.vibFine = (cmd == 0x34)
- if sub > 0 then t.vibSpeed = sub end
- if sd > 0 then t.vibDepth = sd end
- end
- elseif cmd == 0x7 then
- if data > 0 then
- if sub > 0 then t.tremSpeed = sub end
- if sd > 0 then t.tremDepth = sd end
- end
- elseif cmd == 0x9 then
- if t.active then
- local off = data * 256 + t.hiOff * 65536
- local s = t.sample
- if (song.fmt == "IT" or song.fmt == "S3M") and s and off >= s.len and not s.loop then
- off = 0
- end
- t.cursor = off
- end
- elseif cmd == 0xE then
- if sub == 0x1 then slideUp(t, sd * song.slideMul)
- elseif sub == 0x2 then slideDown(t, sd * song.slideMul)
- elseif sub == 0x6 then
- if sd == 0 then t.loopRow = rowNow
- else
- if t.loopCnt == 0 then t.loopCnt = sd else t.loopCnt = t.loopCnt - 1 end
- if t.loopCnt > 0 then loopTarget = t.loopRow end
- end
- elseif sub == 0x4 then t.vibWave = sd
- elseif sub == 0x5 then
- if song.pm == 1 then t.fineOv = (sd > 7) and sd - 16 or sd
- elseif song.fmt == "XM" and song.pm == 3 then t.fineSemi = (sd - 8) / 8 end
- elseif sub == 0x7 then t.tremWave = sd
- elseif sub == 0xA then t.volume = min(64, t.volume + sd)
- elseif sub == 0xB then t.volume = max(0, t.volume - sd)
- elseif sub == 0xC then if sd == 0 then noteCut(t) end
- end
- elseif cmd == 0x10 then
- song.gvol = min(64, data)
- elseif cmd == 0x14 then
- if data == 0 then applyKeyOff(t) end
- elseif cmd == 0x15 then
- t.envPos = data
- elseif cmd == 0x21 then
- if sub == 1 then slideUp(t, sd) elseif sub == 2 then slideDown(t, sd) end
- elseif cmd == 0x2C then
- t.cvol = min(64, data)
- elseif cmd == 0x35 then
- doS7(t, c, sd)
- elseif cmd == 0x36 then
- t.hiOff = sd
- elseif cmd == 0x37 then
- local f, n = dSlide(data)
- t.cvol = clampVol(t.cvol + f)
- elseif cmd == 0x38 or cmd == 0x39 or cmd == 0x3A then
- local f, n = dSlide(data)
- t.volume = clampVol(t.volume + f)
- if song.fastVol then t.volume = clampVol(t.volume + n) end
- end
- else
- if vc == 2 then t.volume = max(0, t.volume - vv)
- elseif vc == 3 then t.volume = min(64, t.volume + vv)
- elseif vc == 12 then slideUp(t, vv * song.slideMul)
- elseif vc == 13 then slideDown(t, vv * song.slideMul)
- elseif vc == 11 then tonePorta(t, t.portSpeed)
- end
- if vc == 7 and cmd ~= 0x4 and cmd ~= 0x6 and cmd ~= 0x34 and cmd ~= 0x39 then doVibrato(t) end
- if cmd == 0x0 then
- if data > 0 then
- local phase = tick % 3
- if phase == 1 then t.arp = sub elseif phase == 2 then t.arp = sd end
- end
- elseif cmd == 0x1 then slideUp(t, data * song.slideMul)
- elseif cmd == 0x2 then slideDown(t, data * song.slideMul)
- elseif cmd == 0x3 or cmd == 0x5 then
- tonePorta(t, t.portSpeed)
- if cmd == 0x5 then t.volume = max(0, min(64, t.volume + slideAmount(data))) end
- elseif cmd == 0x4 or cmd == 0x6 or cmd == 0x34 then
- doVibrato(t)
- if cmd == 0x6 then t.volume = max(0, min(64, t.volume + slideAmount(data))) end
- elseif cmd == 0x7 then
- t.tremOff = waveform(band(t.tremWave, 3), t.tremPos) * t.tremDepth * 4
- t.tremPos = (t.tremPos + t.tremSpeed) % 64
- elseif cmd == 0xA then
- t.volume = max(0, min(64, t.volume + slideAmount(data)))
- elseif cmd == 0x38 then
- local _, n = dSlide(data)
- t.volume = clampVol(t.volume + n)
- elseif cmd == 0x39 then
- local _, n = dSlide(data)
- t.volume = clampVol(t.volume + n)
- doVibrato(t)
- elseif cmd == 0x3A then
- local _, n = dSlide(data)
- t.volume = clampVol(t.volume + n)
- tonePorta(t, t.portSpeed)
- elseif cmd == 0x37 then
- local _, n = dSlide(data)
- t.cvol = clampVol(t.cvol + n)
- elseif cmd == 0x11 then
- song.gvol = max(0, min(64, song.gvol + (sub - sd) * song.gvolScale))
- elseif cmd == 0x1B then
- if sd > 0 and tick % sd == 0 then
- makeGhost(t)
- t.cursor = 0
- t.curScale = 0
- t.volume = retrigVol(t.volume, sub)
- end
- elseif cmd == 0x1D then
- local period = sub + sd + 2
- if t.tremorPos % period >= sub + 1 then t.tremorOff = true end
- t.tremorPos = t.tremorPos + 1
- elseif cmd == 0xE then
- if sub == 0x9 then
- if sd > 0 and tick % sd == 0 then makeGhost(t); t.cursor = 0; t.curScale = 0 end
- elseif sub == 0xC then
- if tick == sd then noteCut(t) end
- end
- elseif cmd == 0x14 then
- if tick == data then applyKeyOff(t) end
- elseif cmd == 0x32 then
- if data < 0x10 then bpm = max(32, bpm - data)
- elseif data < 0x20 then bpm = min(255, bpm + sd) end
- end
- end
- finalizeChannel(t)
- advanceChannel(t)
- end
- local rawBuffer, outputBuffer = {}, {}
- local shapeErr = 0
- local function renderLinear(buf, i0, i1, data, len, cur, step, vol, vinc, hasLoop, ls, le, ll)
- for i = i0, i1 do
- if hasLoop then
- if cur >= le then cur = ls + (cur - le) % ll end
- elseif cur >= len then
- return cur, vol, true
- end
- local ip = floor(cur)
- local v0 = data[ip + 1] or 0
- local v1 = data[ip + 2]
- if hasLoop and ip + 1 >= le then v1 = data[ls + 1] end
- if not v1 then v1 = v0 end
- buf[i] = buf[i] + (v0 + (v1 - v0) * (cur - ip)) * vol
- cur = cur + step
- vol = vol + vinc
- end
- return cur, vol, false
- end
- local function renderNearest(buf, i0, i1, data, len, cur, step, vol, vinc, hasLoop, ls, le, ll)
- for i = i0, i1 do
- if hasLoop then
- if cur >= le then cur = ls + (cur - le) % ll end
- elseif cur >= len then
- return cur, vol, true
- end
- buf[i] = buf[i] + (data[floor(cur) + 1] or 0) * vol
- cur = cur + step
- vol = vol + vinc
- end
- return cur, vol, false
- end
- local render = INTERPOLATE and renderLinear or renderNearest
- local function advanceSilent(count, len, cur, step, hasLoop, ls, le, ll)
- cur = cur + step * count
- if hasLoop then
- if cur >= le then cur = ls + (cur - le) % ll end
- return cur, false
- elseif cur >= len then
- return cur, true
- end
- return cur, false
- end
- local function mixVoice(t, count, invNorm)
- local g = t.ghost
- if g then
- t.ghost = nil
- local n = min(count, RAMP_SAMPLES)
- local v = g.vol * invNorm
- render(rawBuffer, 1, n, g.data, g.len, g.cur, g.step, v, -v / n,
- g.hasLoop, g.ls, g.le, g.ll)
- end
- local s = t.sample
- if t.active and s and s.len > 0 then
- local target = (t.step > 0) and t.volumeScale or 0
- local from = t.curScale
- local hasLoop, ls, ll = getLoop(s, t.keyoff)
- local le = ls + ll
- local cur, ended, _
- if from == 0 and target == 0 then
- cur, ended = advanceSilent(count, s.len, t.cursor, t.step, hasLoop, ls, le, ll)
- else
- local ramp = min(count, RAMP_SAMPLES)
- cur, _, ended = render(rawBuffer, 1, ramp, s.data, s.len, t.cursor, t.step,
- from * invNorm, (target - from) * invNorm / ramp, hasLoop, ls, le, ll)
- if not ended and ramp < count then
- cur, _, ended = render(rawBuffer, ramp + 1, count, s.data, s.len, cur, t.step,
- target * invNorm, 0, hasLoop, ls, le, ll)
- end
- end
- t.cursor = cur
- t.curScale = target
- if ended then t.active = false end
- else
- t.curScale = 0
- end
- end
- local function mixSamples(count)
- local nch = song.channels
- local invNorm = 1 / (sqrt(max(4, min(16, nch))) * 1.1)
- for i = 1, count do rawBuffer[i] = 0 end
- for c = 1, nch do
- local t = tracks[c]
- if t and not t.muted then mixVoice(t, count, invNorm) end
- end
- for i = 1, #bg do mixVoice(bg[i], count, invNorm) end
- for i = #bg, 1, -1 do
- if not bg[i].active then table.remove(bg, i) end
- end
- local err = shapeErr
- local range = 128 - SOFT_KNEE
- for i = 1, count do
- local x = rawBuffer[i]
- local ax = x < 0 and -x or x
- if ax > SOFT_KNEE then
- local u = (ax - SOFT_KNEE) / range
- ax = SOFT_KNEE + range * u / (1 + u)
- x = x < 0 and -ax or ax
- end
- local v = x + err
- local q = floor(v + 0.5)
- if q > 127 then q = 127 elseif q < -128 then q = -128 end
- err = v - q
- if err > 1 or err < -1 then err = 0 end
- outputBuffer[i] = q
- end
- shapeErr = err
- return outputBuffer, count
- end
- local chunkBuffer = {}
- local function flushAudio(buf, bufSize)
- if bufSize == 0 then return end
- local chunkSize = 4096
- for i = 1, bufSize, chunkSize do
- local chunkIdx = 1
- local endRange = min(i + chunkSize - 1, bufSize)
- for j = i, endRange do
- chunkBuffer[chunkIdx] = buf[j]
- chunkIdx = chunkIdx + 1
- end
- local oldLen = #chunkBuffer
- if oldLen >= chunkIdx then
- for j = chunkIdx, oldLen do chunkBuffer[j] = nil end
- end
- while not speaker.playAudio(chunkBuffer) do
- local tmr = os.startTimer(0.25)
- while true do
- local ev, id = os.pullEvent()
- if ev == "speaker_audio_empty" then
- os.cancelTimer(tmr)
- break
- elseif ev == "timer" and id == tmr then
- break
- end
- end
- end
- end
- os.queueEvent("nt_yield")
- os.pullEvent("nt_yield")
- end
- local function playSong()
- local nch = song.channels
- local curOrder, curRow = 1, 1
- local idleSkips = 0
- local acc = 0
- local pending, pendingCount = {}, 0
- local lastOrder = 0
- while true do
- local pIdx = song.order[curOrder]
- local pat = pIdx and song.patterns[pIdx]
- if pIdx and not pat and song.fmt == "XM" then pat = EMPTYPAT end
- if curOrder > song.songLength then
- if not LOOP_SONG then break end
- curOrder, curRow = song.restart, 1
- elseif song.markers and pIdx == 255 then
- if not LOOP_SONG then break end
- curOrder, curRow = song.restart, 1
- idleSkips = idleSkips + 1
- if idleSkips > song.songLength + 1 then break end
- elseif song.markers and pIdx == 254 then
- curOrder, curRow = curOrder + 1, 1
- idleSkips = idleSkips + 1
- if idleSkips > song.songLength + 1 then break end
- elseif not pat then
- curOrder, curRow = curOrder + 1, 1
- idleSkips = idleSkips + 1
- if idleSkips > song.songLength + 1 then break end
- elseif curRow > pat.rows then
- curOrder, curRow = curOrder + 1, 1
- else
- idleSkips = 0
- if curOrder ~= lastOrder then
- lastOrder = curOrder
- for c = 1, nch do
- tracks[c].loopRow, tracks[c].loopCnt = 1, 0
- end
- end
- rowNow = curRow
- pendingOrder, pendingBreak, loopTarget, rowRepeat, fineDelay = nil, nil, nil, 0, 0
- local rowData = pat[curRow] or EMPTYROW
- for c = 1, nch do
- local t = tracks[c]
- local cell = rowData[c] or EMPTY
- local cmd, data = cell.e, cell.p
- if song.letters and cmd >= LE then
- cmd, data = resolveLetter(t, cmd - LE, data)
- elseif song.mem and MEMEFF[cmd] then
- local key = (cmd == 5 or cmd == 6) and 10 or cmd
- if data == 0 then data = t.mem[key] or 0 else t.mem[key] = data end
- end
- t.effect, t.eData, t.vc, t.vv = cmd, data, cell.vc, cell.vv
- t.delayTick, t.delayCell = nil, nil
- if cmd == 0xE then
- local sub, sd = rshift(data, 4), band(data, 15)
- if sub == 0xE then rowRepeat = max(rowRepeat, sd) end
- if sub == 0xD and sd > 0 and (cell.n ~= 0 or cell.i ~= 0) then
- t.delayTick, t.delayCell = sd, cell
- end
- elseif cmd == 0x3E then
- fineDelay = max(fineDelay, data)
- end
- if not t.delayTick then startCell(t, cell, c, cmd) end
- end
- local tick = 0
- while tick < speed * (1 + rowRepeat) + fineDelay do
- for c = 1, nch do processChannelTick(tracks[c], tick, c) end
- for i = 1, #bg do updateBG(bg[i]) end
- acc = acc + MIX_RATE * 2.5 / bpm
- local n = floor(acc)
- acc = acc - n
- if n > 0 then
- local audio, cnt = mixSamples(n)
- for i = 1, cnt do pending[pendingCount + i] = audio[i] end
- pendingCount = pendingCount + cnt
- if pendingCount >= TARGET_CHUNK then
- flushAudio(pending, pendingCount)
- pendingCount = 0
- end
- end
- tick = tick + 1
- end
- if pendingOrder or pendingBreak then
- curOrder = pendingOrder or (curOrder + 1)
- curRow = pendingBreak or 1
- if curOrder > song.songLength then
- if not LOOP_SONG then break end
- curOrder = song.restart
- end
- local np = song.patterns[song.order[curOrder]]
- if np and curRow > np.rows then curRow = 1 end
- elseif loopTarget then
- curRow = loopTarget
- else
- curRow = curRow + 1
- end
- end
- end
- if pendingCount > 0 then flushAudio(pending, pendingCount) end
- end
- local function detectFormat(filename)
- if D:sub(1, 17) == "Extended Module: " then return "XM" end
- if D:sub(1, 4) == "IMPM" then return "IT" end
- if D:sub(45, 48) == "SCRM" then return "S3M" end
- local ext = filename:lower():match("%.(%w+)$")
- if ext == "xm" then return "XM" elseif ext == "it" then return "IT" elseif ext == "s3m" then return "S3M" end
- if #D >= 1084 then return "MOD" end
- return nil
- end
- print("NekoTracker: A ComputerCraft Tracker player by nonogamer9")
- write("Filename: ")
- local filename = read()
- if not fs.exists(filename) then error("File not found: " .. filename) end
- local f = fs.open(filename, "rb")
- D = f.readAll() or ""
- f.close()
- local fmt = detectFormat(filename)
- if not fmt then error("Unrecognised or truncated module file") end
- local loaders = { MOD = loadMOD, S3M = loadS3M, XM = loadXM, IT = loadIT }
- local ok, res = pcall(loaders[fmt])
- D = ""
- if not ok then error("Failed to load " .. fmt .. ": " .. tostring(res)) end
- song = res
- speed, bpm = song.speed, song.tempo
- for c = 1, song.channels do tracks[c] = newTrack(c) end
- print("Format: " .. song.fmt)
- print("Detected Channels: " .. song.channels)
- print("Playing: " .. filename)
- playSong()
- print("Playback finished!")
Add Comment
Please, Sign In to add comment