HertzDevil icon

another mml engine

HertzDevil | PRO | 09/07/16 02:44:54 PM UTC | 0 ⭐ | 628 👁️ | Never ⏰ | []
Lua |

7.15 KB

|

None

|

0 👍

/

0 👎

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

Comments

  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  • Duizxy icon
    05/15/26 01:24:13 PM UTC
    text |

    0 B

    |

    0 👍

    /

    0 👎

    Best Pastebin.com alternatives paste sites list
     Copy and paste the link in a new browser tab and then hit enter!
     https://crazyporn.xxx/members/157354/?asgtbndr=1&play=true&spon=adworld&zone=400