Advertisement
HertzDevil

another mml engine

Sep 7th, 2016
279
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 7.15 KB | None | 0 0
  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
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement