Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- local OUTPUT_RATE = 48000
- local VGM_RATE = 44100
- local AUDIO_CHUNK = 2560
- local DEFAULT_VOLUME = 0.20
- local DEFAULT_AY_CLOCK = 1789773
- local DEFAULT_SCC_CLOCK = 1789773
- local DEFAULT_LOOP_COUNT = 1
- local PSG_OVERSAMPLE = 4
- local SCC_OVERSAMPLE = 4
- local PSG_TONE_DIV = 8
- local SCC_CLOCK_DIV = 16
- local SCC_MIN_PERIOD = 8
- local PSG_GAIN = 1.0
- local SCC_GAIN = 1.0
- local MASTER_GAIN = 1.0
- local DC_BLOCK = true
- local HP_R = 0.995
- local DITHER = true
- local YIELD_SECONDS = 2.0
- local MAX_LOOP_GUARD = 100000
- print("MSXCC! a MSX player for ComputerCraft")
- local speaker = peripheral.find("speaker")
- if not speaker then
- print("No speaker attached!")
- return
- end
- local floor = math.floor
- local min = math.min
- local max = math.max
- local unpack = table.unpack or unpack
- local function u8(s, p)
- return s:byte(p + 1) or 0
- end
- local function u16(s, p)
- return u8(s, p) + u8(s, p + 1) * 256
- end
- local function u32le(s, p)
- return u8(s, p)
- + u8(s, p + 1) * 256
- + u8(s, p + 2) * 65536
- + u8(s, p + 3) * 16777216
- end
- local function signed8(v)
- if v >= 128 then
- return v - 256
- end
- return v
- end
- local function bitOf(v, n)
- return floor(v / (2 ^ n)) % 2
- end
- local function hasBit(v, n)
- return bitOf(v, n) == 1
- end
- local function clamp(v, lo, hi)
- if v < lo then return lo end
- if v > hi then return hi end
- return v
- end
- local function requireBytes(data, pos, count, dataEnd)
- if pos < 1 or pos + count - 1 > dataEnd + 1 then
- error(string.format(
- "Truncated VGM command at offset %X",
- pos - 1
- ))
- end
- end
- local Reader = {}
- Reader.__index = Reader
- function Reader.new(data)
- return setmetatable({
- data = data,
- pos = 1,
- buf = 0,
- bits = 0
- }, Reader)
- end
- function Reader:byte()
- local b = self.data:byte(self.pos)
- if not b then
- error("Unexpected end of DEFLATE stream")
- end
- self.pos = self.pos + 1
- return b
- end
- function Reader:read(n)
- while self.bits < n do
- self.buf = self.buf + self:byte() * (2 ^ self.bits)
- self.bits = self.bits + 8
- end
- local p = 2 ^ n
- local r = self.buf % p
- self.buf = (self.buf - r) / p
- self.bits = self.bits - n
- return r
- end
- function Reader:alignByte()
- local drop = self.bits % 8
- if drop > 0 then
- self:read(drop)
- end
- end
- local function reverseBits(v, n)
- local r = 0
- for _ = 1, n do
- r = r * 2 + (v % 2)
- v = floor(v / 2)
- end
- return r
- end
- local function makeHuffman(lengths)
- local count = {}
- for i = 0, 15 do
- count[i] = 0
- end
- for i = 1, #lengths do
- local l = lengths[i]
- if l > 0 then
- count[l] = count[l] + 1
- end
- end
- local nextCode = {}
- local code = 0
- for bits = 1, 15 do
- code = (code + count[bits - 1]) * 2
- nextCode[bits] = code
- end
- local tree = {}
- for sym = 1, #lengths do
- local l = lengths[sym]
- if l > 0 then
- local c = reverseBits(nextCode[l], l)
- nextCode[l] = nextCode[l] + 1
- local node = tree
- for _ = 1, l do
- local b = c % 2
- c = floor(c / 2)
- if not node[b] then
- node[b] = {}
- end
- node = node[b]
- end
- node.symbol = sym - 1
- end
- end
- return tree
- end
- local function huffRead(r, tree)
- local node = tree
- while node.symbol == nil do
- local b = r:read(1)
- node = node[b]
- if not node then
- error("Invalid DEFLATE Huffman code")
- end
- end
- return node.symbol
- end
- local LENGTH_BASE = {
- 3,4,5,6,7,8,9,10,
- 11,13,15,17,19,23,27,31,
- 35,43,51,59,67,83,99,115,
- 131,163,195,227,258
- }
- local LENGTH_EXTRA = {
- 0,0,0,0,0,0,0,0,
- 1,1,1,1,2,2,2,2,
- 3,3,3,3,4,4,4,4,
- 5,5,5,5,0
- }
- local DIST_BASE = {
- 1,2,3,4,5,7,9,13,
- 17,25,33,49,65,97,129,193,
- 257,385,513,769,1025,1537,2049,3073,
- 4097,6145,8193,12289,16385,24577
- }
- local DIST_EXTRA = {
- 0,0,0,0,1,1,2,2,
- 3,3,4,4,5,5,6,6,
- 7,7,8,8,9,9,10,10,
- 11,11,12,12,13,13
- }
- local FIXED_LIT
- local FIXED_DIST
- do
- local lit = {}
- local dist = {}
- for i = 0, 287 do
- if i <= 143 then
- lit[i + 1] = 8
- elseif i <= 255 then
- lit[i + 1] = 9
- elseif i <= 279 then
- lit[i + 1] = 7
- else
- lit[i + 1] = 8
- end
- end
- for i = 1, 32 do
- dist[i] = 5
- end
- FIXED_LIT = makeHuffman(lit)
- FIXED_DIST = makeHuffman(dist)
- end
- local CL_ORDER = {
- 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15
- }
- local function dynamicTrees(r)
- local hlit = r:read(5) + 257
- local hdist = r:read(5) + 1
- local hclen = r:read(4) + 4
- local cl = {}
- for i = 1, 19 do
- cl[i] = 0
- end
- for i = 1, hclen do
- cl[CL_ORDER[i] + 1] = r:read(3)
- end
- local clTree = makeHuffman(cl)
- local lengths = {}
- local total = hlit + hdist
- while #lengths < total do
- local sym = huffRead(r, clTree)
- if sym <= 15 then
- lengths[#lengths + 1] = sym
- elseif sym == 16 then
- if #lengths == 0 then
- error("Invalid DEFLATE repeat")
- end
- local rep = r:read(2) + 3
- local val = lengths[#lengths]
- for _ = 1, rep do
- lengths[#lengths + 1] = val
- end
- elseif sym == 17 then
- local rep = r:read(3) + 3
- for _ = 1, rep do
- lengths[#lengths + 1] = 0
- end
- elseif sym == 18 then
- local rep = r:read(7) + 11
- for _ = 1, rep do
- lengths[#lengths + 1] = 0
- end
- else
- error("Invalid DEFLATE code length symbol")
- end
- if #lengths > total then
- error("DEFLATE code lengths overflow")
- end
- end
- local lit = {}
- local dist = {}
- local anyDist = false
- for i = 1, hlit do
- lit[i] = lengths[i]
- end
- for i = 1, hdist do
- dist[i] = lengths[hlit + i]
- if dist[i] ~= 0 then
- anyDist = true
- end
- end
- if not anyDist then
- dist[1] = 1
- end
- return makeHuffman(lit), makeHuffman(dist)
- end
- local function inflate(data)
- local r = Reader.new(data)
- local out = {}
- local n = 0
- while true do
- local final = r:read(1)
- local btype = r:read(2)
- if btype == 0 then
- r:alignByte()
- local len = r:byte() + r:byte() * 256
- local nlen = r:byte() + r:byte() * 256
- if ((len + nlen) % 65536) ~= 65535 then
- error("Invalid DEFLATE stored block")
- end
- for _ = 1, len do
- n = n + 1
- out[n] = r:byte()
- end
- elseif btype == 1 or btype == 2 then
- local lt
- local dt
- if btype == 1 then
- lt = FIXED_LIT
- dt = FIXED_DIST
- else
- lt, dt = dynamicTrees(r)
- end
- while true do
- local sym = huffRead(r, lt)
- if sym < 256 then
- n = n + 1
- out[n] = sym
- elseif sym == 256 then
- break
- elseif sym <= 285 then
- local li = sym - 256
- local length = LENGTH_BASE[li]
- local extra = LENGTH_EXTRA[li]
- if extra > 0 then
- length = length + r:read(extra)
- end
- local ds = huffRead(r, dt)
- if ds > 29 then
- error("Invalid DEFLATE distance code")
- end
- local dist = DIST_BASE[ds + 1]
- local de = DIST_EXTRA[ds + 1]
- if de > 0 then
- dist = dist + r:read(de)
- end
- if dist > n then
- error("Invalid DEFLATE back-reference")
- end
- for _ = 1, length do
- n = n + 1
- out[n] = out[n - dist]
- end
- else
- error("Invalid DEFLATE length symbol")
- end
- end
- else
- error("Reserved DEFLATE block type")
- end
- if final == 1 then
- break
- end
- end
- local parts = {}
- for i = 1, n, 4096 do
- parts[#parts + 1] =
- string.char(unpack(out, i, min(i + 4095, n)))
- end
- return table.concat(parts)
- end
- local function gunzip(data)
- if #data < 18 then
- error("VGZ file is too small")
- end
- if u8(data, 0) ~= 0x1F or u8(data, 1) ~= 0x8B then
- error("Not a GZIP file")
- end
- if u8(data, 2) ~= 8 then
- error("GZIP stream is not DEFLATE")
- end
- local flags = u8(data, 3)
- if bitOf(flags, 5) ~= 0
- or bitOf(flags, 6) ~= 0
- or bitOf(flags, 7) ~= 0 then
- error("Invalid GZIP flags")
- end
- local pos = 11
- if hasBit(flags, 2) then
- if pos + 1 > #data then
- error("Truncated GZIP extra field")
- end
- local xlen = u16(data, pos - 1)
- pos = pos + 2 + xlen
- end
- if hasBit(flags, 3) then
- while pos <= #data and u8(data, pos - 1) ~= 0 do
- pos = pos + 1
- end
- pos = pos + 1
- end
- if hasBit(flags, 4) then
- while pos <= #data and u8(data, pos - 1) ~= 0 do
- pos = pos + 1
- end
- pos = pos + 1
- end
- if hasBit(flags, 1) then
- pos = pos + 2
- end
- if pos > #data - 8 then
- error("Invalid or truncated GZIP header")
- end
- return inflate(data:sub(pos, #data - 8))
- end
- local function loadMusic(filename)
- local f = fs.open(filename, "rb")
- if not f then
- error("Could not open: " .. filename)
- end
- local data = f.readAll()
- f.close()
- if not data or #data == 0 then
- error("File is empty")
- end
- if data:sub(1, 4) == "Vgm " then
- return data
- end
- if u8(data, 0) == 0x1F and u8(data, 1) == 0x8B then
- print("Decompressing VGZ...")
- local vgm = gunzip(data)
- if vgm:sub(1, 4) ~= "Vgm " then
- error("Decompressed data is not VGM")
- end
- return vgm
- end
- error("File is neither VGM nor VGZ")
- end
- local AY_LEVELS = {
- [0] = 0.0,
- 0.01375,
- 0.02046,
- 0.02905,
- 0.04234,
- 0.06185,
- 0.08472,
- 0.13691,
- 0.16914,
- 0.26467,
- 0.35272,
- 0.44997,
- 0.57037,
- 0.68728,
- 0.84818,
- 1.0
- }
- local YM_LEVELS = {
- [0] = 0.0,
- [1] = 0.0,
- [2] = 0.00465,
- [3] = 0.00772,
- [4] = 0.01096,
- [5] = 0.01396,
- [6] = 0.01700,
- [7] = 0.02002,
- [8] = 0.02437,
- [9] = 0.02969,
- [10] = 0.03507,
- [11] = 0.04039,
- [12] = 0.04854,
- [13] = 0.05834,
- [14] = 0.06806,
- [15] = 0.07778,
- [16] = 0.09252,
- [17] = 0.11109,
- [18] = 0.12975,
- [19] = 0.14849,
- [20] = 0.17667,
- [21] = 0.21155,
- [22] = 0.24639,
- [23] = 0.28110,
- [24] = 0.33373,
- [25] = 0.40043,
- [26] = 0.46738,
- [27] = 0.53443,
- [28] = 0.63517,
- [29] = 0.75801,
- [30] = 0.87993,
- [31] = 1.0
- }
- local function envelopeReload(p)
- local shape = p.envShape
- p.envHolding = false
- p.envFirst = true
- p.envReverse = false
- if hasBit(shape, 2) then
- p.envLevel = 0
- else
- p.envLevel = p.envSteps - 1
- end
- end
- local function envelopeTick(p)
- if p.envHolding then
- return
- end
- local shape = p.envShape
- local continueMode = hasBit(shape, 3)
- local attack = hasBit(shape, 2)
- local alternate = hasBit(shape, 1)
- local hold = hasBit(shape, 0)
- local top = p.envSteps - 1
- local directionUp = attack
- if p.envReverse then
- directionUp = not directionUp
- end
- if directionUp then
- if p.envLevel < top then
- p.envLevel = p.envLevel + 1
- return
- end
- else
- if p.envLevel > 0 then
- p.envLevel = p.envLevel - 1
- return
- end
- end
- if not continueMode then
- p.envLevel = 0
- p.envHolding = true
- p.envFirst = false
- return
- end
- if hold then
- if alternate and p.envFirst then
- if attack then
- p.envLevel = 0
- else
- p.envLevel = top
- end
- end
- p.envHolding = true
- p.envFirst = false
- return
- end
- if alternate then
- p.envReverse = not p.envReverse
- if p.envReverse then
- if attack then
- p.envLevel = top - 1
- else
- p.envLevel = 1
- end
- else
- if attack then
- p.envLevel = 1
- else
- p.envLevel = top - 1
- end
- end
- else
- if attack then
- p.envLevel = 0
- else
- p.envLevel = top
- end
- end
- p.envFirst = false
- end
- local function psgWrite(p, r, v)
- r = r % 16
- v = v % 256
- if r == 1 or r == 3 or r == 5 then
- v = v % 16
- elseif r == 6 then
- v = v % 32
- elseif r >= 8 and r <= 10 then
- v = v % 32
- elseif r == 13 then
- v = v % 16
- end
- p.reg[r] = v
- if r <= 5 then
- local ch = floor(r / 2) + 1
- local period =
- p.reg[(ch - 1) * 2]
- + (p.reg[(ch - 1) * 2 + 1] % 16) * 256
- if period < 1 then
- period = 1
- end
- p.tonePeriod[ch] = period
- local limit = period
- if p.toneCounter[ch] >= limit then
- p.toneCounter[ch] =
- p.toneCounter[ch] % limit
- end
- elseif r == 6 then
- local period = v % 32
- if period < 1 then
- period = 1
- end
- p.noisePeriod = period
- elseif r == 7 then
- for ch = 1, 3 do
- p.toneEn[ch] =
- not hasBit(v, ch - 1)
- p.noiseEn[ch] =
- not hasBit(v, ch + 2)
- end
- elseif r >= 8 and r <= 10 then
- local ch = r - 7
- p.useEnv[ch] = hasBit(v, 4)
- local level = v % 16
- if p.isYM then
- p.fixedLevel[ch] =
- YM_LEVELS[level == 0 and 0 or (level * 2 + 1)]
- else
- p.fixedLevel[ch] = AY_LEVELS[level]
- end
- elseif r == 11 or r == 12 then
- local ep =
- p.reg[11] +
- p.reg[12] * 256
- if ep < 1 then
- ep = 1
- end
- p.envPeriod = ep
- elseif r == 13 then
- p.envShape = v % 16
- envelopeReload(p)
- end
- end
- local function newPSG(clock, chipType, flags)
- local isYM =
- chipType == 0x10
- or chipType == 0x11
- or chipType == 0x12
- or chipType == 0x13
- local effClock = clock
- local subRate = OUTPUT_RATE * PSG_OVERSAMPLE
- local p = {
- isYM = isYM,
- clock = effClock,
- reg = {},
- tonePeriod = {1, 1, 1},
- toneCounter = {0, 0, 0},
- toneOut = {0, 0, 0},
- toneEn = {false, false, false},
- noiseEn = {false, false, false},
- fixedLevel = {0, 0, 0},
- useEnv = {false, false, false},
- noisePeriod = 1,
- noiseCounter = 0,
- noiseOut = 1,
- lfsr = 1,
- envPeriod = 1,
- envCounter = 0,
- envShape = 0,
- envLevel = 0,
- envSteps = isYM and 32 or 16,
- envHolding = false,
- envFirst = true,
- envReverse = false,
- toneStep =
- effClock / PSG_TONE_DIV / subRate,
- noiseStep =
- effClock / 16 / subRate,
- envStep =
- effClock /
- (isYM and 8 or 16) /
- subRate,
- envLUT = isYM and YM_LEVELS or AY_LEVELS
- }
- for i = 0, 15 do
- p.reg[i] = 0
- end
- for i = 0, 15 do
- psgWrite(p, i, 0)
- end
- psgWrite(p, 7, 0xFF)
- p.lfsr = 1
- p.noiseOut = 1
- return p
- end
- local function psgSample(p)
- local acc = 0
- local tonePeriod = p.tonePeriod
- local toneCounter = p.toneCounter
- local toneOut = p.toneOut
- for _ = 1, PSG_OVERSAMPLE do
- for ch = 1, 3 do
- local period = tonePeriod[ch]
- local counter =
- toneCounter[ch] +
- p.toneStep
- local limit = period
- while counter >= limit do
- counter = counter - limit
- toneOut[ch] = 1 - toneOut[ch]
- end
- toneCounter[ch] = counter
- end
- local nc =
- p.noiseCounter +
- p.noiseStep
- local np = p.noisePeriod
- while nc >= np do
- nc = nc - np
- local l = p.lfsr
- p.noiseOut = l % 2
- local feedback =
- ((l % 2) +
- (floor(l / 8) % 2)) % 2
- l = floor(l / 2)
- if feedback == 1 then
- l = l + 65536
- end
- p.lfsr = l
- end
- p.noiseCounter = nc
- local ec =
- p.envCounter +
- p.envStep
- local ep = p.envPeriod
- while ec >= ep do
- ec = ec - ep
- envelopeTick(p)
- end
- p.envCounter = ec
- local sum = 0
- local noise = p.noiseOut
- for ch = 1, 3 do
- local tone =
- p.toneEn[ch]
- and toneOut[ch]
- or 1
- local nz =
- p.noiseEn[ch]
- and noise
- or 1
- local level
- if p.useEnv[ch] then
- level = p.envLUT[p.envLevel]
- else
- level = p.fixedLevel[ch]
- end
- sum = sum + tone * nz * level
- end
- acc = acc + sum
- end
- return acc /
- (PSG_OVERSAMPLE * 3)
- end
- local function newSCC(clock, plus)
- local s = {
- clock = clock,
- plus = plus,
- wave = {},
- period = {},
- volume = {},
- enabled = {},
- phase = {},
- inc = {}
- }
- for ch = 1, 5 do
- s.wave[ch] = {}
- for i = 1, 32 do
- s.wave[ch][i] = 0
- end
- s.period[ch] = 0
- s.volume[ch] = 0
- s.enabled[ch] = false
- s.phase[ch] = 0
- s.inc[ch] = 0
- end
- return s
- end
- local function sccUpdateInc(s, ch)
- local period = s.period[ch]
- s.inc[ch] =
- 32 * s.clock /
- (SCC_CLOCK_DIV * (period + 1) *
- OUTPUT_RATE * SCC_OVERSAMPLE)
- end
- local function sccWrite(s, port, addr, value)
- value = value % 256
- if port == 0 then
- if addr < 0x80 then
- local ch =
- floor(addr / 32) + 1
- local index =
- (addr % 32) + 1
- local v = signed8(value)
- s.wave[ch][index] = v
- if not s.plus and ch == 4 then
- s.wave[5][index] = v
- end
- end
- elseif port == 4 then
- if s.plus and addr < 0xA0 then
- local ch =
- floor(addr / 32) + 1
- local index =
- (addr % 32) + 1
- s.wave[ch][index] =
- signed8(value)
- end
- elseif port == 1 then
- if addr < 10 then
- local ch =
- floor(addr / 2) + 1
- local p = s.period[ch]
- if addr % 2 == 0 then
- p =
- (p - p % 256) +
- value
- else
- p =
- (value % 16) * 256 +
- (p % 256)
- end
- s.period[ch] = p
- sccUpdateInc(s, ch)
- end
- elseif port == 2 then
- if addr < 5 then
- s.volume[addr + 1] =
- value % 16
- end
- elseif port == 3 then
- if addr == 0 then
- for ch = 1, 5 do
- s.enabled[ch] =
- hasBit(value, ch - 1)
- end
- end
- elseif port == 5 then
- end
- end
- local function sccSample(s)
- local acc = 0
- for _ = 1, SCC_OVERSAMPLE do
- for ch = 1, 5 do
- if s.enabled[ch] and s.period[ch] > SCC_MIN_PERIOD then
- local ph =
- s.phase[ch] +
- s.inc[ch]
- if ph >= 32 then
- ph = ph % 32
- end
- s.phase[ch] = ph
- local sample =
- s.wave[ch][floor(ph) + 1]
- acc =
- acc +
- sample *
- s.volume[ch]
- end
- end
- end
- return acc /
- (128 *
- 15 *
- 5 *
- SCC_OVERSAMPLE)
- end
- local audioLastYield = os.clock()
- local function flushAudio(buf)
- if buf.n == 0 then
- return
- end
- while not speaker.playAudio(buf) do
- os.pullEvent("speaker_audio_empty")
- end
- for i = buf.n, 1, -1 do
- buf[i] = nil
- end
- buf.n = 0
- if os.clock() - audioLastYield >= YIELD_SECONDS then
- os.queueEvent("msxplayer_yield")
- os.pullEvent("msxplayer_yield")
- audioLastYield = os.clock()
- end
- end
- local filter = {
- x = 0,
- y = 0
- }
- local function resetAudioFilter()
- filter.x = 0
- filter.y = 0
- end
- local function renderSamples(chips, count, volume, buf)
- local n = buf.n
- local psgs = chips.psg
- local sccs = chips.scc
- local px = filter.x
- local py = filter.y
- local random = math.random
- for _ = 1, count do
- local x = 0
- if psgs then
- for id = 0, 1 do
- local p = psgs[id]
- if p then
- x =
- x +
- psgSample(p) *
- (p.outputGain or 1) *
- PSG_GAIN
- end
- end
- end
- if sccs then
- for id = 0, 1 do
- local s = sccs[id]
- if s then
- x =
- x +
- sccSample(s) *
- (s.outputGain or 1) *
- SCC_GAIN
- end
- end
- end
- local y
- if DC_BLOCK then
- y =
- x -
- px +
- HP_R * py
- px = x
- py = y
- else
- y = x
- end
- local v =
- y *
- volume *
- MASTER_GAIN *
- 127
- if DITHER then
- v =
- v +
- random() -
- random()
- end
- if v >= 0 then
- v = floor(v + 0.5)
- else
- v = -floor(-v + 0.5)
- end
- v = clamp(v, -128, 127)
- n = n + 1
- buf[n] = v
- end
- buf.n = n
- filter.x = px
- filter.y = py
- end
- local sampleAccumulator = 0
- local function resetTiming()
- sampleAccumulator = 0
- end
- local function renderTicks(chips, ticks, volume, buf)
- if ticks <= 0 then
- return
- end
- sampleAccumulator =
- sampleAccumulator +
- ticks *
- OUTPUT_RATE /
- VGM_RATE
- local samples =
- floor(sampleAccumulator)
- sampleAccumulator =
- sampleAccumulator -
- samples
- while samples > 0 do
- local amount =
- min(
- samples,
- AUDIO_CHUNK - buf.n
- )
- renderSamples(
- chips,
- amount,
- volume,
- buf
- )
- samples =
- samples -
- amount
- if buf.n >= AUDIO_CHUNK then
- flushAudio(buf)
- end
- end
- end
- local AY_NAMES = {
- [0x00] = "AY-3-8910",
- [0x01] = "AY-3-8912",
- [0x02] = "AY-3-8913",
- [0x03] = "AY8930",
- [0x04] = "AY-3-8914",
- [0x10] = "YM2149",
- [0x11] = "YM3439",
- [0x12] = "YMZ284",
- [0x13] = "YMZ294"
- }
- local function parseExtraHeader(data, h)
- h.secondClock = {
- ay = nil,
- scc = nil
- }
- h.volumeModifier = 1.0
- if h.version < 0x170 then
- return
- end
- if h.dataOffset <= 0x100 then
- return
- end
- if #data < 0x10C then
- return
- end
- local extraStart = 0x100
- local extraSize =
- u32le(data, extraStart)
- if extraSize < 4 then
- return
- end
- if extraStart + extraSize > #data then
- return
- end
- local clockOffset =
- u32le(data, extraStart + 4)
- local volumeOffset =
- u32le(data, extraStart + 8)
- if clockOffset ~= 0 then
- local p =
- extraStart +
- clockOffset
- if p + 1 <= #data then
- local count =
- u8(data, p)
- p = p + 1
- for _ = 1, count do
- if p + 4 >= #data then
- break
- end
- local chipId =
- u8(data, p)
- local clock =
- u32le(data, p + 1)
- p = p + 5
- if chipId == 0x12 then
- h.secondClock.ay = clock
- elseif chipId == 0x19 then
- h.secondClock.scc = clock
- end
- end
- end
- end
- if h.version >= 0x160 and #data >= 0x80 then
- local modifier = u8(data, 0x7C)
- if modifier ~= 0 then
- local m = modifier
- if m >= 128 then
- m = m - 256
- end
- if m == -63 then
- m = -64
- end
- h.volumeModifier =
- 2 ^ (m / 32)
- end
- end
- end
- local function parseHeader(data)
- if data:sub(1, 4) ~= "Vgm " then
- error("Invalid VGM header")
- end
- if #data < 0x40 then
- error("VGM file is too small")
- end
- local version =
- u32le(data, 0x08)
- local eofRel =
- u32le(data, 0x04)
- local dataOffset = 0x40
- if version >= 0x150 then
- local rel =
- u32le(data, 0x34)
- if rel ~= 0 then
- dataOffset =
- 0x34 + rel
- end
- end
- if dataOffset < 0x40
- or dataOffset >= #data then
- error("Invalid VGM data offset")
- end
- local dataEnd
- if eofRel ~= 0 then
- dataEnd =
- min(
- #data,
- eofRel + 4
- )
- else
- dataEnd = #data
- end
- local ayRaw = 0
- local sccRaw = 0
- if version >= 0x151 and #data >= 0x78 then
- ayRaw =
- u32le(data, 0x74)
- end
- if version >= 0x161 and #data >= 0xA0 then
- sccRaw =
- u32le(data, 0x9C)
- end
- local ayClock =
- ayRaw % 0x40000000
- local sccClock =
- sccRaw % 0x40000000
- local h = {
- version = version,
- dataOffset = dataOffset,
- dataEnd = dataEnd,
- loopOffset = nil,
- ayClock = ayClock,
- ayDual = hasBit(ayRaw, 30),
- ayHardPan = hasBit(ayRaw, 31),
- ayType = 0,
- ayFlags = 0,
- sccClock = sccClock,
- sccDual = hasBit(sccRaw, 30),
- sccPlus = hasBit(sccRaw, 31),
- secondClock = {},
- volumeModifier = 1.0
- }
- if #data >= 0x20 then
- local loopRel =
- u32le(data, 0x1C)
- if loopRel ~= 0 then
- local loopOffset =
- 0x1C + loopRel
- if loopOffset >= dataOffset
- and loopOffset < dataEnd then
- h.loopOffset =
- loopOffset
- end
- end
- end
- if version >= 0x151
- and #data >= 0x7A then
- h.ayType =
- u8(data, 0x78)
- h.ayFlags =
- u8(data, 0x79)
- end
- parseExtraHeader(data, h)
- return h
- end
- local function ensurePSG(chips, id, h)
- if not chips.psg then
- chips.psg = {}
- end
- if chips.psg[id] then
- return chips.psg[id]
- end
- local clock
- if id == 0 then
- clock =
- h.ayClock ~= 0
- and h.ayClock
- or DEFAULT_AY_CLOCK
- else
- clock =
- h.secondClock.ay
- or h.ayClock
- or DEFAULT_AY_CLOCK
- end
- local p =
- newPSG(
- clock,
- h.ayType,
- h.ayFlags
- )
- chips.psg[id] = p
- return p
- end
- local function ensureSCC(chips, id, h)
- if not chips.scc then
- chips.scc = {}
- end
- if chips.scc[id] then
- return chips.scc[id]
- end
- local clock
- if id == 0 then
- clock =
- h.sccClock ~= 0
- and h.sccClock
- or nil
- else
- clock =
- h.secondClock.scc
- or h.sccClock
- or nil
- end
- if not clock or clock == 0 then
- if h.ayClock and h.ayClock ~= 0 then
- clock = h.ayClock
- else
- clock = DEFAULT_SCC_CLOCK
- end
- end
- local plus = h.sccPlus
- local s =
- newSCC(
- clock,
- plus
- )
- chips.scc[id] = s
- return s
- end
- local STREAM_LEN = {
- [0x90] = 5,
- [0x91] = 5,
- [0x92] = 6,
- [0x93] = 11,
- [0x94] = 2,
- [0x95] = 5
- }
- local function skipLength(cmd)
- if cmd == 0x00 then
- return 1
- end
- if cmd == 0x4F
- or cmd == 0x50 then
- return 2
- end
- if cmd >= 0x30
- and cmd <= 0x3F then
- return 2
- end
- if cmd >= 0x40
- and cmd <= 0x4E then
- return 3
- end
- if cmd >= 0x51
- and cmd <= 0x5F then
- return 3
- end
- if cmd >= 0xA1
- and cmd <= 0xBF then
- return 3
- end
- if cmd >= 0xC0
- and cmd <= 0xDF then
- return 4
- end
- if cmd >= 0xE0
- and cmd <= 0xFF then
- return 5
- end
- return STREAM_LEN[cmd]
- end
- local function playVGM(filename, userVolume, loopCount)
- print("Loading " .. filename .. "...")
- local data =
- loadMusic(filename)
- local h =
- parseHeader(data)
- print(string.format(
- "VGM %d.%02X",
- floor(h.version / 256),
- h.version % 256
- ))
- local chips = {
- psg = {},
- scc = {}
- }
- local psgCount = 0
- local sccCount = 0
- if h.ayClock ~= 0 then
- ensurePSG(chips, 0, h)
- psgCount = 1
- if h.ayDual then
- ensurePSG(chips, 1, h)
- psgCount = 2
- end
- print(
- (AY_NAMES[h.ayType] or "PSG")
- .. " #1 clock: "
- .. h.ayClock
- .. " Hz"
- )
- if h.ayDual then
- print(
- (AY_NAMES[h.ayType] or "PSG")
- .. " #2 clock: "
- .. (
- h.secondClock.ay
- or h.ayClock
- )
- .. " Hz"
- )
- end
- end
- if h.sccClock ~= 0 then
- ensureSCC(chips, 0, h)
- sccCount = 1
- if h.sccDual then
- ensureSCC(chips, 1, h)
- sccCount = 2
- end
- print(
- (
- h.sccPlus
- and "K052539 SCC+"
- or "K051649 SCC"
- )
- .. " #1 clock: "
- .. h.sccClock
- .. " Hz"
- )
- if h.sccDual then
- print(
- (
- h.sccPlus
- and "K052539 SCC+"
- or "K051649 SCC"
- )
- .. " #2 clock: "
- .. (
- h.secondClock.scc
- or h.sccClock
- )
- .. " Hz"
- )
- end
- end
- local position =
- h.dataOffset + 1
- local loopPosition
- if h.loopOffset then
- loopPosition =
- h.loopOffset + 1
- end
- local loopsPlayed = 0
- local totalTicks = 0
- local len62 = 735
- local len63 = 882
- local buf = {
- n = 0
- }
- resetTiming()
- resetAudioFilter()
- local volume =
- userVolume *
- h.volumeModifier
- volume = clamp(volume, 0, 3.0)
- local function wait(t)
- if t <= 0 then
- return
- end
- renderTicks(
- chips,
- t,
- volume,
- buf
- )
- totalTicks =
- totalTicks + t
- end
- print("Playing...")
- while position <= h.dataEnd do
- local cmd =
- u8(data, position - 1)
- if cmd == 0x66 then
- if loopPosition
- and loopsPlayed < loopCount
- and loopsPlayed < MAX_LOOP_GUARD then
- loopsPlayed =
- loopsPlayed + 1
- position =
- loopPosition
- else
- break
- end
- elseif cmd == 0x61 then
- requireBytes(
- data,
- position,
- 3,
- h.dataEnd
- )
- wait(
- u16(data, position)
- )
- position =
- position + 3
- elseif cmd == 0x62 then
- wait(len62)
- position =
- position + 1
- elseif cmd == 0x63 then
- wait(len63)
- position =
- position + 1
- elseif cmd == 0x64 then
- requireBytes(
- data,
- position,
- 4,
- h.dataEnd
- )
- local c =
- u8(data, position)
- local n =
- u16(data, position + 1)
- if c == 0x62 then
- len62 = n
- elseif c == 0x63 then
- len63 = n
- end
- position =
- position + 4
- elseif cmd >= 0x70
- and cmd <= 0x7F then
- wait(
- (cmd % 16) + 1
- )
- position =
- position + 1
- elseif cmd >= 0x80
- and cmd <= 0x8F then
- wait(cmd % 16)
- position =
- position + 1
- elseif cmd == 0xA0 then
- requireBytes(
- data,
- position,
- 3,
- h.dataEnd
- )
- local rawReg =
- u8(data, position)
- local chipId = 0
- if h.ayDual
- and hasBit(rawReg, 7) then
- chipId = 1
- end
- local reg =
- rawReg % 0x80
- if reg < 16 then
- local p =
- ensurePSG(
- chips,
- chipId,
- h
- )
- psgWrite(
- p,
- reg,
- u8(data, position + 1)
- )
- end
- position =
- position + 3
- elseif cmd == 0xD2 then
- requireBytes(
- data,
- position,
- 4,
- h.dataEnd
- )
- local rawPort =
- u8(data, position)
- local chipId = 0
- if h.sccDual
- and hasBit(rawPort, 7) then
- chipId = 1
- end
- local port =
- rawPort % 0x80
- local s =
- ensureSCC(
- chips,
- chipId,
- h
- )
- sccWrite(
- s,
- port,
- u8(data, position + 1),
- u8(data, position + 2)
- )
- position =
- position + 4
- elseif cmd == 0x67 then
- requireBytes(
- data,
- position,
- 7,
- h.dataEnd
- )
- if u8(data, position) ~= 0x66 then
- error("Malformed VGM data block")
- end
- local size =
- u32le(data, position + 3)
- size =
- size % 2147483648
- if size > h.dataEnd - position - 6 then
- error("VGM data block exceeds file")
- end
- local total =
- 7 + size
- requireBytes(
- data,
- position,
- total,
- h.dataEnd
- )
- position =
- position + total
- elseif cmd == 0x68 then
- requireBytes(
- data,
- position,
- 12,
- h.dataEnd
- )
- position =
- position + 12
- elseif cmd == 0x00 then
- position =
- position + 1
- else
- local len =
- skipLength(cmd)
- if not len then
- error(string.format(
- "Unsupported VGM command %02X at offset %X",
- cmd,
- position - 1
- ))
- end
- requireBytes(
- data,
- position,
- len,
- h.dataEnd
- )
- position =
- position + len
- end
- end
- flushAudio(buf)
- print(string.format(
- "Done. %.3f seconds.",
- totalTicks / VGM_RATE
- ))
- if loopsPlayed > 0 then
- print(
- "Extra loops played: "
- .. loopsPlayed
- )
- end
- end
- local args = {...}
- local filename =
- args[1]
- if not filename then
- write("VGM/VGZ filename: ")
- filename = read()
- end
- if not filename
- or filename == "" then
- print("No filename supplied.")
- return
- end
- if not fs.exists(filename) then
- print(
- "File not found: "
- .. filename
- )
- return
- end
- write(
- "Volume (0.0-3.0, default "
- .. DEFAULT_VOLUME
- .. "): "
- )
- local volume =
- tonumber(read())
- if not volume
- or volume < 0
- or volume > 3 then
- volume =
- DEFAULT_VOLUME
- end
- write(
- "Extra loops (default "
- .. DEFAULT_LOOP_COUNT
- .. "): "
- )
- local loopCount =
- tonumber(read())
- if not loopCount
- or loopCount < 0 then
- loopCount =
- DEFAULT_LOOP_COUNT
- end
- loopCount =
- floor(loopCount)
- loopCount =
- min(loopCount, MAX_LOOP_GUARD)
- local ok, err =
- pcall(
- playVGM,
- filename,
- volume,
- loopCount
- )
- if not ok then
- print("")
- print("Playback error:")
- print(err)
- end
Advertisement
Add Comment
Please, Sign In to add comment