SHARE
TWEET

another mml engine

HertzDevil Sep 7th, 2016 128 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. local require = require
  2.  
  3. local Default = require "default"
  4. local MML = require "mml"
  5. local Music = require "music"
  6. local Class = require "util.class"
  7. local ChunkNum = require "music.chunk.num"
  8. local Warning = require "util.warning"
  9.  
  10. local Check = Default.Errors.RuntimeCheck
  11. local ParamAssert = Default.Errors.ParamCheck
  12. local CmdAssert = Default.Errors.CommandCheck
  13.  
  14. local builder = MML.CmdBuilder()
  15.  
  16. function builder:quickcmd (mmlname, outbyte, ...)
  17.   builder:setHandler(function (ch, ...)
  18.     ch:addChunk(outbyte, ...)
  19.   end)
  20.   for _, v in ipairs {...} do builder:param(v) end
  21.   return builder:make(mmlname)
  22. end
  23.  
  24. local CHANNELS = 4
  25. local engine = Default.Engine(CHANNELS)
  26.  
  27. local DURATION = {[1] = 32, [2] = 16, [4] = 8, [8] = 4, [16] = 2, [32] = 1}
  28.  
  29. local lengthLexer; do
  30.   local getvalue = function (sv)
  31.     local raw = sv:trim "%%" ~= nil
  32.     local l = sv:trim "%d*"
  33.     if raw then return 0, tonumber(l) end
  34.     sv:ws()
  35.     local dot = 2 - .5 ^ #sv:trim "%.*"
  36.     if l ~= "" then
  37.       return 0, ParamAssert(DURATION[tonumber(l)]) * dot
  38.     end
  39.     return dot, 0
  40.   end
  41. lengthLexer = function (sv)
  42.   local mult, add = getvalue(sv)
  43.   while sv:trim "%s*&%s*" ~= nil do
  44.     local a, b = getvalue(sv)
  45.     mult, add = mult + a, add + b
  46.   end
  47.   return function (x)
  48.     local ticks = mult * x + add
  49.     local z = math.floor(ticks)
  50.     CmdAssert(z == ticks and z >= 1 and z <= 256, "Invalid note duration")
  51.     if z == 256 then z = 0 end
  52.     return z
  53.   end
  54. end; end
  55.  
  56. local Pointer = Class({
  57.   __init = function (self, dest, name)
  58.     self.__super.__init(self, dest, name, 2)
  59.   end,
  60.   compile = function (self)
  61.     local s = CmdAssert(self.dest, "Unknown pointer destination")
  62.     local label = CmdAssert(s:getLabel(self.name), "Unknown pointer label")
  63.     return ChunkNum(s:getBase() + label, "2<"):compile()
  64.   end,
  65. }, require "music.chunk.pointer")
  66.  
  67. -- table with numeric keys sorted ascending for iteration
  68. local NumSort = function ()
  69.   local order = {}
  70.   return setmetatable({}, {
  71.     __metatable = "NumSort",
  72.     __newindex = function (t, k, v)
  73.       rawset(t, k, v)
  74.       if not order[1] then order[1] = k; return end
  75.       if k < order[1] then table.insert(order, 1, k); return end
  76.       if k > order[#order] then order[#order + 1] = k; return end
  77.       local b, e = 1, #order
  78.       while e - b > 1 do
  79.         local m = math.ceil((b + e) / 2)
  80.         if k < order[m] then e = m
  81.         elseif k > order[m] then b = m
  82.         else break end
  83.       end
  84.       table.insert(order, e, k)
  85.     end,
  86.     __len = function (t)
  87.       return #order
  88.     end,
  89.     __pairs = function (t)
  90.       return coroutine.wrap(function ()
  91.         for _, v in ipairs(order) do
  92.           coroutine.yield(v, t[v])
  93.         end
  94.       end)
  95.     end,
  96.   })
  97. end
  98.  
  99.  
  100.  
  101. -- Channel state
  102.  
  103. local Channel = engine.channel.__mt.__index
  104.  
  105. function Channel:__init (...)
  106.   self.__super.__init(self, ...)
  107.   self.muted = false
  108.   self.key = {c = 0, d = 0, e = 0, f = 0, g = 0, a = 0, b = 0}
  109.   self.lastnote = nil
  110.   self.duration = Music.State(4)
  111.   self.octave = Music.State(2)
  112.   self.gate = 0
  113.   self.patterns = NumSort()
  114.   self.anonpatterns = {}
  115. end
  116.  
  117. function Channel:after ()
  118.   self:addChunk(0x00)
  119. end
  120.  
  121.  
  122.  
  123. -- MML command table
  124.  
  125. builder:setTable(engine.parser.macros)
  126.  
  127. builder:setHandler(Class.call "addChunk"):param "Uint8":variadic():make "`"
  128.  
  129. for name, val in pairs {c = 1, d = 3, e = 5, f = 6, g = 8, a = 10, b = 12} do
  130.   builder:setHandler(function (ch, acc, durfunc)
  131.     local n = val + (acc.neutral and 0 or ch.key[name])
  132.             + acc.shift + ch.octave:get() * 12 + 0x80
  133.     CmdAssert(n >= 0x81 and n <= 0xBC, "Note out of range")
  134.     if n < 0x8A then
  135.       Warning.warn "Notes below A-1 will not produce any sound in this engine"
  136.     end
  137.     if not ch.muted then
  138.       local l = durfunc(DURATION[ch.duration:get()])
  139.       if l == 1 then
  140.         ch:addChunk(n, l)
  141.       else
  142.         local lreal = math.max(1, l - ch.gate)
  143.         ch:addChunk(n, lreal)
  144.         if l > lreal then
  145.           ch:addChunk(0x80, l - lreal)
  146.         end
  147.       end
  148.     end
  149.     ch.lastnote = n
  150.   end):param "Acc":param(lengthLexer):make(name)
  151. end
  152.  
  153. builder:setHandler(function (ch, t)
  154.   for k, v in pairs(t) do
  155.     ch.key[k] = v
  156.   end
  157. end):param "KeySig":make "k"
  158. builder:setHandler(function (ch, x)
  159.   ch.muted = x
  160. end):param "Bool":make "m"
  161. builder:setHandler(function (ch, x)
  162.   CmdAssert(DURATION[x], "Invalid default note duration")
  163.   ch.duration:set(x)
  164. end):param "Uint8":make "l"
  165. builder:setHandler(function (ch, x)
  166.   ch.gate = x
  167. end):param "Uint8":make "q"
  168. builder:setHandler(function (ch)
  169.   local n = ch.octave:get()
  170.   CmdAssert(n > 0, "Octave out of range")
  171.   ch.octave:set(n - 1)
  172. end):make "<"
  173. builder:setHandler(function (ch)
  174.   local n = ch.octave:get()
  175.   CmdAssert(n < 4, "Octave out of range")
  176.   ch.octave:set(n + 1)
  177. end):make ">"
  178. builder:setHandler(function (ch, x)
  179.   CmdAssert(x <= 4, "Octave out of range")
  180.   ch.octave:set(x)
  181. end):param "Uint8":make "o"
  182.  
  183. builder:setHandler(function (ch, x)
  184.   CmdAssert(x <= 3, "Invalid duty setting")
  185.   ch:addChunk(0x03, x * 0x40 + 0x30, 0xA0, 0x21)
  186. end):param "Uint8":make "W"
  187. builder:setHandler(function (ch, id)
  188.   local s = ch:pushStream()
  189.   if not id then
  190.     table.insert(ch.anonpatterns, s)
  191.   else
  192.     CmdAssert(not ch.patterns[id], "Duplicate pattern index")
  193.     ch.patterns[id] = s
  194.   end
  195. end):param "Uint":optional():make "["
  196. builder:setHandler(function (ch, x)
  197.   CmdAssert(x >= 1 and x <= 256, "Invalid loop count")
  198.   if x == 256 then x = 0 end
  199.   local s = ch:popStream()
  200.   s:push(0x05)
  201.   ch:addChunk(0x04, x, Pointer(s, "START"))
  202. end):param "Uint":optional "1":make "]"
  203. builder:setHandler(function (ch, id, x)
  204.   CmdAssert(x >= 1 and x <= 256, "Invalid loop count")
  205.   if x == 256 then x = 0 end
  206.   local s = CmdAssert(ch.patterns[id], "Unknown pattern index")
  207.   ch:addChunk(0x04, x, Pointer(s, "START"))
  208. end):param "Uint":param "Uint":optional "1":make "[]"
  209. builder:quickcmd("V", 0x0C, "Uint8")
  210. builder:quickcmd("_", 0x12, "Int8")
  211. builder:quickcmd("@V", 0x16, "Uint8", "Uint8", "Uint8")
  212.  
  213.  
  214.  
  215. -- Directive table
  216.  
  217. builder:setTable(engine.parser.directives)
  218.  
  219.  
  220.  
  221. -- Inserter function
  222.  
  223. engine.inserter = function (rom, song, track)
  224.   local header = Music.Stream()
  225.   header:addLabel "TARGET"
  226.   song:doAll(function (ch)
  227.     header:push(Pointer(ch.stream, "START"))
  228.   end)
  229.  
  230.   rom:seek("set", 0x23E4 + 8 * (track - 1))
  231.   header:setBase(rom:seek() + 0x7F80)
  232.   local pos = rom:read(1):byte()
  233.   pos = pos + rom:read(1):byte() * 0x100
  234.   rom:seek("cur", -2)
  235.  
  236.   local s = pos
  237.   song:doAll(function (ch)
  238.     ch.stream:setBase(s)
  239.     s = s + ch.stream.size
  240.     for _, v in pairs(ch.patterns) do
  241.       v:setBase(s)
  242.       s = s + v.size
  243.     end
  244.     for _, v in ipairs(ch.anonpatterns) do
  245.       v:setBase(s)
  246.       s = s + v.size
  247.     end
  248.   end)
  249.  
  250.   rom:write(header:build())
  251.   rom:seek("set", pos - 0x7F80)
  252.   song:doAll(function (ch)
  253.     rom:write(ch.stream:build())
  254.     for _, v in pairs(ch.patterns) do
  255.       rom:write(v:build())
  256.     end
  257.     for _, v in ipairs(ch.anonpatterns) do
  258.       rom:write(v:build())
  259.     end
  260.   end)
  261. end
  262.  
  263.  
  264.  
  265. -- export module
  266.  
  267. builder.quickcmd = nil
  268.  
  269. return engine
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
Top