Created
August 19, 2016 03:29
-
-
Save HertzDevil/036304c692b0f26b7a9d7cfe1126a0ac to your computer and use it in GitHub Desktop.
MDRV2 MDT to MML unconverter
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- 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]) |
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
how would i use this?