Skip to content

Instantly share code, notes, and snippets.

@HertzDevil
Created August 19, 2016 03:29
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save HertzDevil/036304c692b0f26b7a9d7cfe1126a0ac to your computer and use it in GitHub Desktop.
Save HertzDevil/036304c692b0f26b7a9d7cfe1126a0ac to your computer and use it in GitHub Desktop.
MDRV2 MDT to MML unconverter
-- MDT filename is the only command line argument
local schar = function (f)
local z = string.byte(f:read(1))
return z >= 0x80 and z - 0x100 or z
end
local char = function (f)
return string.byte(f:read(1))
end
local sshort = function (f)
local z = string.byte(f:read(1))
z = z + string.byte(f:read(1)) * 0x100
return z >= 0x8000 and z - 0x10000 or z
end
local short = function (f)
local z = string.byte(f:read(1))
z = z + string.byte(f:read(1)) * 0x100
return z
end
local MML_CH_IDENT = {
[0x80] = "A", [0x81] = "B", [0x82] = "C", [0x83] = "D", [0x84] = "E", [0x85] = "F",
[0x40] = "I", [0x41] = "J", [0x42] = "K",
[0x10] = "L",
}
local MML_NOTE_STR = {"c", "c+", "d", "d+", "e", "f", "f+", "g", "g+", "a", "a+", "b"}
local MML_TIME_STR = {}
for _, v in ipairs({1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 192}) do
MML_TIME_STR[v] = tostring(math.floor(192 / v))
end
local parseTrack = function (f)
local song = {channel = {}, pattern = {}, FM = {}, SSG = {}}
char(f)
char(f)
local chCount = short(f)
song.chip = short(f)
assert(song.chip <= 2)
for i = 1, chCount do
local c = {pos = {}, event = {}}
c.loc = short(f)
c.id = short(f)
if c.id ~= 0 then
table.insert(song.channel, c)
end
end
local FMloc = short(f)
local SSGloc = short(f)
local titleLoc = short(f)
local FMcount = 0
local SSGcount = 0
local octave
local octStack = {}
local patternID = {}
local registerPattern = function (chID)
local id = #song.pattern
local loc = short(f)
if not patternID[loc] then
patternID[loc] = id
table.insert(song.pattern, {pos = {}, event = {}, loc = loc, id = chID, patternID = id})
end
return patternID[loc]
end
local timeStr = function (x) -- add dotted notes
return (MML_TIME_STR[x] or "%" .. x)
end
local noteStr = function (ch)
local str = MML_NOTE_STR[bit32.band(ch, 0x0F) + 1]
local shift = bit32.rshift(ch, 4) - octave
if math.abs(shift) >= 2 then
str = "O" .. octave + shift .. str
else
str = (shift > 0 and ">" or shift < 0 and "<" or "") .. str
end
octave = octave + shift
return str
end
local param = function (x)
local l = {}
for i = 1, x do l[#l + 1] = char(f) end
return table.unpack(l)
end
local macro = {}
macro[0x90] = function () return {str = "r" .. timeStr(char(f))} end
macro[0x91] = function () return {str = "&"} end
macro[0xE0] = function () octStack[#octStack + 1] = false; return {str = "|:", param(1)} end
macro[0xE1] = function () if octStack[#octStack] == false then octStack[#octStack] = octave end; return {str = ":"} end
macro[0xE2] = function () octave = octStack[#octStack] or octave; octStack[#octStack] = nil; return {str = ":|"} end
macro[0xE3] = function () return {str = "/"} end
macro[0xE4] = function () return {str = "[", param(1)} end
macro[0xE5] = function () return {str = "]"} end
macro[0xE6] = function () return {str = "^", schar(f)} end
macro[0xE7] = function () return {str = "@^", schar(f)} end
macro[0xE8] = function () local a, b, c, d = param(4); return {str = "SA", a, 0, b, c, d} end
macro[0xE9] = function () return {str = "t", param(1)} end
macro[0xEA] = function () return {str = "Q", param(1)} end
macro[0xEB] = function (t)
local c = param(1)
if bit32.btest(t.id, 0x40) then c = c - 0x80; SSGcount = math.max(SSGcount, c + 1)
elseif bit32.btest(t.id, 0x80) then FMcount = math.max(FMcount, c + 1) end
return {str = "@", c}
end
macro[0xEC] = function (t)
if bit32.btest(t.id, 0x10) then
local c = param(1)
return bit32.btest(c, 0x80) and {str = "@V", c - 0x80, param(1)} or {str = "V", c, param(6)}
else
return {str = "@V", param(1)}
end
end
macro[0xED] = function () return {str = "S", param(4)} end
macro[0xEE] = function () return {str = "Y", param(2)} end
macro[0xEF] = function () return {str = "W", param(1)} end
macro[0xF0] = function () local c = param(1); return {str = "_", bit32.btest(c, 0x80) and 0x80 - c or c} end
macro[0xF1] = function (t) return {str = "P", param(bit32.btest(t.id, 0x10) and 2 or 1)} end
macro[0xF4] = function () return {str = "@V+", param(1)} end
macro[0xF5] = function () return {str = "@V-", param(1)} end
macro[0xF6] = function () return {str = "[:", (param(3))} end
macro[0xF7] = function () param(3); return {str = ":]"} end
macro[0xF8] = function () return {str = "Z", param(1)} end
macro[0xF9] = function () param(2); return {str = "|"} end
macro[0xFA] = function (t) return {str = "U", registerPattern(t.id)} end
macro[0xFB] = function () local a, b, c, d = param(4); return {str = "SP", a, b, 0, c, d} end
macro[0xFC] = function () local a, b, c, d = param(4); return {str = "SA", a, b, 0, c, d} end
macro[0xFD] = function () return {str = "SH", param(4)} end
local processChannel = function (v)
octave = 0xFF -- very large
f:seek("set", v.loc)
while true do
local ch = char(f)
if ch == 0xFF then break end
local position = f:seek()
local e = {str = ""}
if ch <= 0x7F then
e.str = noteStr(ch) .. timeStr(char(f))
elseif ch == 0xF3 then
v.loop = sshort(f); v.loop = f:seek() + v.loop
elseif ch == 0xF2 then
local orig = char(f)
local time = char(f)
local offset = sshort(f) * time
local new = bit32.band(orig, 0x0F)
new = math.floor(144 * 440 * 2 ^ (17 + (new - 9) / 12) / 8e6 + .5) + offset
local oct = math.floor(new / 617) - 1
new = new % 617 + 617
new = math.floor(12 * (math.log(new * 8e6 / 144 / 440, 2) - 17) + 9)
new = bit32.rshift(orig, 4) * 12 + bit32.band(orig, 0x0F) + new - bit32.band(orig, 0x0F) + oct * 12
new = bit32.lshift(math.floor(new / 12), 4) + new % 12
e.str = "(" .. bit32.rshift(orig, 4) .. MML_NOTE_STR[bit32.band(orig, 0x0F) + 1]
e.str = e.str .. "," .. bit32.rshift(new, 4) .. MML_NOTE_STR[bit32.band(new, 0x0F) + 1] .. ")" .. timeStr(time)
elseif macro[ch] then
e = macro[ch](v)
end
if ch ~= 0xF3 then
table.insert(v.pos, position)
v.event[position] = e
end
end
end
song.title = ""
f:seek("set", titleLoc)
repeat
local ch = f:read(1)
song.title = song.title .. ch
until ch == "$"
for _, v in ipairs(song.channel) do
processChannel(v)
end
for _, v in ipairs(song.pattern) do
processChannel(v)
end
f:seek("set", FMloc)
while f:seek() < SSGloc do
local b = table.pack(param(32))
local t = {}
t[4], t[11] = b[1] % 0x40, bit32.rshift(b[1], 6)
t[5] = b[2]
t[7] = b[3]
t[3], t[2] = b[4] % 0x8, bit32.rshift(b[4], 3)
t[1], t[10] = b[5] % 0x40, bit32.rshift(b[5], 6)
t[9], t[8] = b[6] % 0x10, bit32.rshift(b[6], 4)
t[6] = b[31] % 0x80
for i, k in ipairs({0, 2, 1, 3}) do
t[i * 11 + 8], t[i * 11 + 9] = b[k + 7] % 0x10, bit32.rshift(b[k + 7] >= 0x80 and 0x140 - b[k + 7] or b[k + 7], 4)
t[i * 11 + 6] = b[k + 11]
t[i * 11 + 1], t[i * 11 + 7] = b[k + 15] % 0x40, bit32.rshift(b[k + 15], 6)
t[i * 11 + 2], t[i * 11 + 11] = b[k + 19] % 0x80, bit32.rshift(b[k + 19], 7)
t[i * 11 + 3], t[i * 11 + 10] = b[k + 23] % 0x40, bit32.rshift(b[k + 23], 6)
t[i * 11 + 4], t[i * 11 + 5] = b[k + 27] % 0x10, bit32.rshift(b[k + 27], 4)
end
song.FM[#song.FM + 1] = t
end
local fsize = f:seek("end")
f:seek("set", SSGloc)
while f:seek() < fsize do
song.SSG[#song.SSG + 1] = table.pack(param(6))
end
return song
end
local writeTrack = function (f, t)
f:write("T=" .. t.title)
f:write("\r\n\r\n")
local writeChannel = function (t)
local header = t.patternID and "#" .. t.patternID or MML_CH_IDENT[t.id]
f:write(header .. "\t")
if t.patternID then
f:write("$" .. (bit32.btest(t.id, 0x80) and "F" or bit32.btest(t.id, 0x40) and "S" or "R") .. " ")
end
local macro = {}
for _, i in ipairs(t.pos) do
if t.loop == i then f:write("\\ ") end
f:write(t.event[i].str)
f:write(table.concat(t.event[i], ","))
--f:write("\r\n" .. header)
f:write(" ")
end
f:write("\r\n")
end
f:write("A " .. (t.chip == 0 and "OPM" or t.chip == 1 and "OPN" or "OPLL") .. "\r\n")
for _, v in ipairs(t.channel) do
writeChannel(v)
end
f:write("\r\n")
for _, v in ipairs(t.pattern) do
writeChannel(v)
end
f:write("\r\n")
for i, v in ipairs(t.FM) do
local n = tostring(i - 1)
for k = 1, #v, 11 do
f:write(k == 1 and "@" .. n .. " = " or string.rep(" ", 4 + #n))
for j = 0, 10 do f:write(v[k + j] .. ",") end
f:write("\r\n")
end
end
f:write("\r\n")
for i, v in ipairs(t.SSG) do
f:write("P" .. i - 1 .. " = " .. table.concat(v, ", ") .. "\r\n")
end
f:write("\r\n")
f:write("\x1A")
end
local dumpTrack = function (fname)
local f = assert(io.open(fname, "rb"))
local m = assert(io.open(fname .. ".MD2", "wb"))
writeTrack(m, parseTrack(f))
f:close()
m:close()
end
dumpTrack(arg[1])
@KongoraFan
Copy link

how would i use this?

@SilSinn9801
Copy link

how would i use this?

On UNIX or Linux Terminal: dump.lua "mdtfilename.mdt"
On Windows: download & install a Lua interpreter, then open a Command Prompt, change to the directory where lua.exe exists, then run lua dump.lua "mdtfilename.mdt"
Alternatively on Windows PowerShell, do .\lua dump.lua "mdtfilename.mdt"
In all cases, mdtfilename.mdt should be renamed to your actual MDT file (REIMU.MDT, ALICE.MDT, etc.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment