Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- local require = require
- local Default = require "default"
- local MML = require "mml"
- local Music = require "music"
- local Class = require "util.class"
- local ChunkNum = require "music.chunk.num"
- local Warning = require "util.warning"
- local Check = Default.Errors.RuntimeCheck
- local ParamAssert = Default.Errors.ParamCheck
- local CmdAssert = Default.Errors.CommandCheck
- local builder = MML.CmdBuilder()
- function builder:quickcmd (mmlname, outbyte, ...)
- builder:setHandler(function (ch, ...)
- ch:addChunk(outbyte, ...)
- end)
- for _, v in ipairs {...} do builder:param(v) end
- return builder:make(mmlname)
- end
- local CHANNELS = 4
- local engine = Default.Engine(CHANNELS)
- local DURATION = {[1] = 32, [2] = 16, [4] = 8, [8] = 4, [16] = 2, [32] = 1}
- local lengthLexer; do
- local getvalue = function (sv)
- local raw = sv:trim "%%" ~= nil
- local l = sv:trim "%d*"
- if raw then return 0, tonumber(l) end
- sv:ws()
- local dot = 2 - .5 ^ #sv:trim "%.*"
- if l ~= "" then
- return 0, ParamAssert(DURATION[tonumber(l)]) * dot
- end
- return dot, 0
- end
- lengthLexer = function (sv)
- local mult, add = getvalue(sv)
- while sv:trim "%s*&%s*" ~= nil do
- local a, b = getvalue(sv)
- mult, add = mult + a, add + b
- end
- return function (x)
- local ticks = mult * x + add
- local z = math.floor(ticks)
- CmdAssert(z == ticks and z >= 1 and z <= 256, "Invalid note duration")
- if z == 256 then z = 0 end
- return z
- end
- end; end
- local Pointer = Class({
- __init = function (self, dest, name)
- self.__super.__init(self, dest, name, 2)
- end,
- compile = function (self)
- local s = CmdAssert(self.dest, "Unknown pointer destination")
- local label = CmdAssert(s:getLabel(self.name), "Unknown pointer label")
- return ChunkNum(s:getBase() + label, "2<"):compile()
- end,
- }, require "music.chunk.pointer")
- -- table with numeric keys sorted ascending for iteration
- local NumSort = function ()
- local order = {}
- return setmetatable({}, {
- __metatable = "NumSort",
- __newindex = function (t, k, v)
- rawset(t, k, v)
- if not order[1] then order[1] = k; return end
- if k < order[1] then table.insert(order, 1, k); return end
- if k > order[#order] then order[#order + 1] = k; return end
- local b, e = 1, #order
- while e - b > 1 do
- local m = math.ceil((b + e) / 2)
- if k < order[m] then e = m
- elseif k > order[m] then b = m
- else break end
- end
- table.insert(order, e, k)
- end,
- __len = function (t)
- return #order
- end,
- __pairs = function (t)
- return coroutine.wrap(function ()
- for _, v in ipairs(order) do
- coroutine.yield(v, t[v])
- end
- end)
- end,
- })
- end
- -- Channel state
- local Channel = engine.channel.__mt.__index
- function Channel:__init (...)
- self.__super.__init(self, ...)
- self.muted = false
- self.key = {c = 0, d = 0, e = 0, f = 0, g = 0, a = 0, b = 0}
- self.lastnote = nil
- self.duration = Music.State(4)
- self.octave = Music.State(2)
- self.gate = 0
- self.patterns = NumSort()
- self.anonpatterns = {}
- end
- function Channel:after ()
- self:addChunk(0x00)
- end
- -- MML command table
- builder:setTable(engine.parser.macros)
- builder:setHandler(Class.call "addChunk"):param "Uint8":variadic():make "`"
- for name, val in pairs {c = 1, d = 3, e = 5, f = 6, g = 8, a = 10, b = 12} do
- builder:setHandler(function (ch, acc, durfunc)
- local n = val + (acc.neutral and 0 or ch.key[name])
- + acc.shift + ch.octave:get() * 12 + 0x80
- CmdAssert(n >= 0x81 and n <= 0xBC, "Note out of range")
- if n < 0x8A then
- Warning.warn "Notes below A-1 will not produce any sound in this engine"
- end
- if not ch.muted then
- local l = durfunc(DURATION[ch.duration:get()])
- if l == 1 then
- ch:addChunk(n, l)
- else
- local lreal = math.max(1, l - ch.gate)
- ch:addChunk(n, lreal)
- if l > lreal then
- ch:addChunk(0x80, l - lreal)
- end
- end
- end
- ch.lastnote = n
- end):param "Acc":param(lengthLexer):make(name)
- end
- builder:setHandler(function (ch, t)
- for k, v in pairs(t) do
- ch.key[k] = v
- end
- end):param "KeySig":make "k"
- builder:setHandler(function (ch, x)
- ch.muted = x
- end):param "Bool":make "m"
- builder:setHandler(function (ch, x)
- CmdAssert(DURATION[x], "Invalid default note duration")
- ch.duration:set(x)
- end):param "Uint8":make "l"
- builder:setHandler(function (ch, x)
- ch.gate = x
- end):param "Uint8":make "q"
- builder:setHandler(function (ch)
- local n = ch.octave:get()
- CmdAssert(n > 0, "Octave out of range")
- ch.octave:set(n - 1)
- end):make "<"
- builder:setHandler(function (ch)
- local n = ch.octave:get()
- CmdAssert(n < 4, "Octave out of range")
- ch.octave:set(n + 1)
- end):make ">"
- builder:setHandler(function (ch, x)
- CmdAssert(x <= 4, "Octave out of range")
- ch.octave:set(x)
- end):param "Uint8":make "o"
- builder:setHandler(function (ch, x)
- CmdAssert(x <= 3, "Invalid duty setting")
- ch:addChunk(0x03, x * 0x40 + 0x30, 0xA0, 0x21)
- end):param "Uint8":make "W"
- builder:setHandler(function (ch, id)
- local s = ch:pushStream()
- if not id then
- table.insert(ch.anonpatterns, s)
- else
- CmdAssert(not ch.patterns[id], "Duplicate pattern index")
- ch.patterns[id] = s
- end
- end):param "Uint":optional():make "["
- builder:setHandler(function (ch, x)
- CmdAssert(x >= 1 and x <= 256, "Invalid loop count")
- if x == 256 then x = 0 end
- local s = ch:popStream()
- s:push(0x05)
- ch:addChunk(0x04, x, Pointer(s, "START"))
- end):param "Uint":optional "1":make "]"
- builder:setHandler(function (ch, id, x)
- CmdAssert(x >= 1 and x <= 256, "Invalid loop count")
- if x == 256 then x = 0 end
- local s = CmdAssert(ch.patterns[id], "Unknown pattern index")
- ch:addChunk(0x04, x, Pointer(s, "START"))
- end):param "Uint":param "Uint":optional "1":make "[]"
- builder:quickcmd("V", 0x0C, "Uint8")
- builder:quickcmd("_", 0x12, "Int8")
- builder:quickcmd("@V", 0x16, "Uint8", "Uint8", "Uint8")
- -- Directive table
- builder:setTable(engine.parser.directives)
- -- Inserter function
- engine.inserter = function (rom, song, track)
- local header = Music.Stream()
- header:addLabel "TARGET"
- song:doAll(function (ch)
- header:push(Pointer(ch.stream, "START"))
- end)
- rom:seek("set", 0x23E4 + 8 * (track - 1))
- header:setBase(rom:seek() + 0x7F80)
- local pos = rom:read(1):byte()
- pos = pos + rom:read(1):byte() * 0x100
- rom:seek("cur", -2)
- local s = pos
- song:doAll(function (ch)
- ch.stream:setBase(s)
- s = s + ch.stream.size
- for _, v in pairs(ch.patterns) do
- v:setBase(s)
- s = s + v.size
- end
- for _, v in ipairs(ch.anonpatterns) do
- v:setBase(s)
- s = s + v.size
- end
- end)
- rom:write(header:build())
- rom:seek("set", pos - 0x7F80)
- song:doAll(function (ch)
- rom:write(ch.stream:build())
- for _, v in pairs(ch.patterns) do
- rom:write(v:build())
- end
- for _, v in ipairs(ch.anonpatterns) do
- rom:write(v:build())
- end
- end)
- end
- -- export module
- builder.quickcmd = nil
- return engine
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement