Advertisement
Guest User

Untitled

a guest
Jul 27th, 2017
64
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 13.20 KB | None | 0 0
  1.  
  2. local FlMMLTimeBase = 384
  3. local VolMacroSymbols = "ABCDEFGHIJKLMNOPQRSTUVWabcdefghijklmnopqrstuvw"
  4. local LenMacroSymbols = "XYZxyz"
  5. local VolMacroStep = 1
  6. assert(VolMacroStep == 1 or VolMacroStep == 3)
  7.  
  8. function round(d)
  9.     return math.floor(d + 0.5)
  10. end
  11.  
  12. function clip(minValue, d, maxValue)
  13.     return math.max(minValue, math.min(d, maxValue))
  14. end
  15.  
  16. function ReadWaveFile(path)
  17.     local read1 = function(file)
  18.         return string.byte(file:read(1))
  19.     end
  20.     local read2 = function(file)
  21.         local l = read1(file)
  22.         local h = read1(file)
  23.         return h * 0x100 + l
  24.     end
  25.     local read2s = function(file)
  26.         local value = read2(file)
  27.         if value < 0x8000 then
  28.             return value
  29.         else
  30.             return value - 0x10000
  31.         end
  32.     end
  33.     local read4 = function(file)
  34.         local l = read2(file)
  35.         local h = read2(file)
  36.         return h * 0x10000 + l
  37.     end
  38.  
  39.     local wave = {}
  40.     local info = {}
  41.  
  42.     local file = io.open(path, "rb")
  43.     if file == nil then
  44.         return wave, info
  45.     end
  46.  
  47.     -- verify RIFF/WAVE header
  48.     if file:read(4) ~= "RIFF" then
  49.         return wave, info
  50.     end
  51.     file:seek("cur", 4)
  52.     if file:read(8) ~= "WAVEfmt " then
  53.         return wave, info
  54.     end
  55.     local fmtChunkSize = read4(file)
  56.  
  57.     -- parse format header
  58.     info.format = read2(file)
  59.     info.channels = read2(file)
  60.     info.samplerate = read4(file)
  61.     local bytesPerSeconds = read4(file)
  62.     local bytesPerBlock = read2(file) -- bytes/sample * channels
  63.     info.bitwidth = read2(file)
  64.     file:seek("cur", fmtChunkSize - 0x10)
  65.  
  66.     -- check unsupported format
  67.     if info.format ~= 1 or (info.bitwidth ~= 8 and info.bitwidth ~= 16) then
  68.         return wave, info
  69.     end
  70.  
  71.     -- data chunk
  72.     if file:read(4) ~= "data" then
  73.         return wave, info
  74.     end
  75.     local dataChunkSize = read4(file)
  76.     assert(dataChunkSize % bytesPerBlock == 0,
  77.         "Invalid data chunk size (" .. dataChunkSize .. "/" .. bytesPerBlock .. ")")
  78.  
  79.     -- read whole samples to array
  80.     for sampleIndex = 1, dataChunkSize / bytesPerBlock do
  81.         for channelIndex = 1, info.channels do
  82.             local value
  83.             if info.bitwidth == 8 then
  84.                 value = (read1(file) - 0x80) / 0x80
  85.             elseif info.bitwidth == 16 then
  86.                 value = read2s(file) / 0x8000
  87.             else
  88.                 error("Unsupported bitwidth " .. info.bitwidth)
  89.             end
  90.  
  91.             if wave[channelIndex] == nil then
  92.                 wave[channelIndex] = {}
  93.             end
  94.             wave[channelIndex][sampleIndex] = value
  95.         end
  96.     end
  97.  
  98.     return wave, info
  99. end
  100.  
  101. -- [ volume converter ] ---------------
  102.  
  103. assert(#VolMacroSymbols % 2 == 0)
  104. local VolMacroPatternCount = #VolMacroSymbols / 2
  105.  
  106. function MakeVolCommandAbs(targetVolume)
  107.     return "@v" .. targetVolume
  108. end
  109.  
  110. function MakeVolCommandRel(curVolume, targetVolume)
  111.     local diff = targetVolume - curVolume
  112.     local distance = math.abs(diff)
  113.     local volDiffInc, volDiffDec = "(", ")"
  114.     if diff < 0 then
  115.         volDiffInc, volDiffDec = volDiffDec, volDiffInc
  116.     end
  117.  
  118.     local symbolIndex
  119.     if VolMacroStep == 3 then
  120.         symbolIndex = math.floor((distance + 1) / 3)
  121.         if distance > (3 * VolMacroPatternCount + 1) then
  122.             symbolIndex = math.floor(symbolIndex / 2)
  123.         end
  124.     else
  125.         symbolIndex = distance - 2
  126.         if distance > (VolMacroPatternCount + 2) then
  127.             symbolIndex = math.floor(distance / 2) - 2
  128.         end
  129.     end
  130.     if diff < 0 then
  131.         symbolIndex = symbolIndex + VolMacroPatternCount
  132.     end
  133.     local macroSymbol = ""
  134.     if symbolIndex >= 1 and symbolIndex <= #VolMacroSymbols then
  135.         macroSymbol = VolMacroSymbols:sub(symbolIndex, symbolIndex)
  136.     end
  137.  
  138.     if distance == 0 then
  139.         return ""
  140.     elseif distance == 1 then
  141.         return volDiffInc
  142.     elseif distance == 2 then
  143.         return volDiffInc .. volDiffInc
  144.     else
  145.         if VolMacroStep == 3 then
  146.             if distance <= (3 * VolMacroPatternCount + 1) then
  147.                 if distance % 3 == 0 then
  148.                     assert(macroSymbol ~= "")
  149.                     return "$" .. macroSymbol
  150.                 elseif distance % 3 == 1 then
  151.                     assert(macroSymbol ~= "")
  152.                     return volDiffInc .. "$" .. macroSymbol
  153.                 else -- if distance % 3 == 2 then
  154.                     assert(macroSymbol ~= "")
  155.                     return volDiffDec .. "$" .. macroSymbol
  156.                 end
  157.             elseif distance % 6 == 0 and distance <= 6 * VolMacroPatternCount then
  158.                 assert(macroSymbol ~= "")
  159.                 return "$" .. macroSymbol .. "$" .. macroSymbol
  160.             else
  161.                 return MakeVolCommandAbs(targetVolume)
  162.             end
  163.         else
  164.             if distance <= (VolMacroPatternCount + 2) then
  165.                 assert(macroSymbol ~= "")
  166.                 return "$" .. macroSymbol
  167.             elseif distance % 2 == 0 and distance >= 6 and distance <= 2 * (VolMacroPatternCount + 2) then
  168.                 assert(macroSymbol ~= "")
  169.                 return "$" .. macroSymbol .. "$" .. macroSymbol
  170.             else
  171.                 return MakeVolCommandAbs(targetVolume)
  172.             end
  173.         end
  174.     end
  175. end
  176.  
  177. function MakeVolCommand(curVolume, targetVolume)
  178.     local volRel = MakeVolCommandRel(curVolume, targetVolume)
  179.     local volAbs = MakeVolCommandAbs(targetVolume)
  180.     if #volRel <= #volAbs then
  181.         return volRel
  182.     else
  183.         return volAbs
  184.     end
  185. end
  186.  
  187. local VolMacroDefs = ""
  188. if VolMacroPatternCount > 0 then
  189.     local volIncMacros, volDecMacros = "", ""
  190.     for index = 1, VolMacroPatternCount do
  191.         volIncMacros = volIncMacros .. "$" .. VolMacroSymbols:sub(index, index) .. "="
  192.         volDecMacros = volDecMacros .. "$" .. VolMacroSymbols:sub(VolMacroPatternCount + index, VolMacroPatternCount + index) .. "="
  193.  
  194.         if VolMacroStep == 3 then
  195.             for i = 1, index do
  196.                 volIncMacros = volIncMacros .. "((("
  197.                 volDecMacros = volDecMacros .. ")))"
  198.             end
  199.         else
  200.             volIncMacros = volIncMacros .. "(("
  201.             volDecMacros = volDecMacros .. "))"
  202.             for i = 1, index do
  203.                 volIncMacros = volIncMacros .. "("
  204.                 volDecMacros = volDecMacros .. ")"
  205.             end
  206.         end
  207.  
  208.         volIncMacros = volIncMacros .. ";"
  209.         volDecMacros = volDecMacros .. ";"
  210.     end
  211.     VolMacroDefs = volIncMacros .. volDecMacros
  212. end
  213.  
  214. -- [ length converter ] ---------------
  215.  
  216. local SamplesPerNote = 96
  217. assert(FlMMLTimeBase % SamplesPerNote == 0)
  218.  
  219. local LengthToNoteTable = {}
  220. -- reset
  221. LengthToNoteTable[1] = "c"
  222. for sampLen = 2, SamplesPerNote * 2 do
  223.     LengthToNoteTable[sampLen] = ""
  224. end
  225. -- single note + dots
  226. for sampLen = 2, SamplesPerNote do
  227.     if SamplesPerNote % sampLen == 0 then
  228.         local baseLen = SamplesPerNote / sampLen
  229.         LengthToNoteTable[sampLen] = "c" .. baseLen
  230.  
  231.         local len = sampLen
  232.         local dots = ""
  233.         local dotLen = sampLen
  234.         while dotLen % 2 == 0 do
  235.             dotLen = dotLen / 2
  236.             dots = dots .. "."
  237.  
  238.             len = len + dotLen
  239.             if len <= #LengthToNoteTable then
  240.                 assert(LengthToNoteTable[len] == "")
  241.                 LengthToNoteTable[len] = "c" .. baseLen .. dots
  242.             end
  243.         end
  244.     end
  245. end
  246. -- tick
  247. for sampLen = 2, #LengthToNoteTable do
  248.     local tick = sampLen * (FlMMLTimeBase / SamplesPerNote)
  249.     local tickstring = "c" .. "%" .. tostring(tick)
  250.     if LengthToNoteTable[sampLen] == "" or #LengthToNoteTable[sampLen] > #tickstring then
  251.         LengthToNoteTable[sampLen] = tickstring
  252.     end
  253. end
  254. -- continuous notes
  255. for sampLen = 2, #LengthToNoteTable do
  256.     if LengthToNoteTable[sampLen] == "" or #LengthToNoteTable[sampLen] > sampLen then
  257.         local note = ""
  258.         for index = 1, sampLen do
  259.             note = note .. "c"
  260.         end
  261.         LengthToNoteTable[sampLen] = note
  262.     end
  263. end
  264. -- long note
  265. for sampLen = SamplesPerNote + 1, SamplesPerNote * 2 do
  266.     local note = "c" .. LengthToNoteTable[sampLen - SamplesPerNote]
  267.     if #note <= #LengthToNoteTable[sampLen] then
  268.         LengthToNoteTable[sampLen] = note
  269.     end
  270. end
  271. -- combination
  272. for sampLen = 3, #LengthToNoteTable do
  273.     if #LengthToNoteTable[sampLen] > 2 then
  274.         for i = 1, math.floor(sampLen / 2) do
  275.             j = sampLen - i
  276.             local note = LengthToNoteTable[j] .. LengthToNoteTable[i]
  277.             if #note < #LengthToNoteTable[sampLen] then
  278.                 LengthToNoteTable[sampLen] = note
  279.             end
  280.         end
  281.     end
  282. end
  283. -- macros
  284. local LenMacroDefs = ""
  285. if #LenMacroSymbols > 0 then
  286.     for index = 1, #LenMacroSymbols do
  287.         local sampLen = index + 2
  288.         local macroSymbol =LenMacroSymbols:sub(index, index)
  289.         LenMacroDefs = LenMacroDefs .. "$" .. macroSymbol .. "=" .. LengthToNoteTable[sampLen] .. ";"
  290.         LengthToNoteTable[sampLen] = "$" .. macroSymbol
  291.     end
  292. end
  293. -- combination
  294. for sampLen = 3, #LengthToNoteTable do
  295.     if #LengthToNoteTable[sampLen] > 2 then
  296.         for i = 1, math.floor(sampLen / 2) do
  297.             j = sampLen - i
  298.             local note = LengthToNoteTable[j] .. LengthToNoteTable[i]
  299.             if #note < #LengthToNoteTable[sampLen] then
  300.                 LengthToNoteTable[sampLen] = note
  301.             end
  302.         end
  303.     end
  304. end
  305.  
  306. function MakeLenCommand(sampLen)
  307.     local tick = sampLen * (FlMMLTimeBase / SamplesPerNote)
  308.     local noteAbs = "c" .. "%" .. tostring(tick)
  309.  
  310.     local len = sampLen
  311.     local noteRel = ""
  312.     while len > SamplesPerNote * 2 do
  313.         len = len - (SamplesPerNote * 2)
  314.         noteRel = noteRel .. "c1c1"
  315.     end
  316.     noteRel = noteRel .. LengthToNoteTable[len]
  317.  
  318.     if #noteRel <= #noteAbs then
  319.         return noteRel
  320.     else
  321.         return noteAbs
  322.     end
  323. end
  324.  
  325. -- [ core subroutines ] ---------------
  326.  
  327. local ShowHistogram = false
  328.  
  329. function SevenBitiseReal(d)
  330.     return clip(0, (d + 1.0) * 0x40, 0x80)
  331. end
  332.  
  333. function SevenBitise(d)
  334.     return clip(0, math.floor((d + 1.0) * 0x40), 0x7f)
  335. end
  336.  
  337. function ShowUsage()
  338.     local options = {
  339.         { name = "-L, -R", description = "specify target channel (left/right)" }
  340.     }
  341.  
  342.     print("FlPCM [options] filename.wav (filename.mml)")
  343.     print("Options:")
  344.     for index = 1, #options do
  345.         print(string.format("%s\t%s", options[index].name, options[index].description))
  346.     end
  347.     return
  348. end
  349.  
  350. function PreprocessPCM(PCM)
  351.     local PCM7 = {}
  352.     local curVolume = SevenBitiseReal(PCM[1])
  353.     for index = 1, #PCM do
  354.         local nextVolume = SevenBitiseReal(PCM[index])
  355.         local diff = nextVolume - curVolume
  356.         local distance = math.abs(diff)
  357.         PCM7[index] = clip(0, round(curVolume + diff), 0x7f)
  358.         curVolume = nextVolume
  359.     end
  360.     return PCM7
  361. end
  362.  
  363. function MakeHistogram(PCM7)
  364.     local VolHistogram = {}
  365.     local LenHistogram = {}
  366.     for index = 1, 127 do
  367.         VolHistogram[index] = 0
  368.     end
  369.     for index = 1, SamplesPerNote * 2 + 1 do
  370.         LenHistogram[index] = 0
  371.     end
  372.  
  373.     if #PCM7 <= 1 then
  374.         return VolHistogram, LenHistogram
  375.     end
  376.  
  377.     local curVolume = PCM7[1]
  378.     local sampLen = 0
  379.     for index = 1, #PCM7 do
  380.         local nextVolume = PCM7[index]
  381.         local diff = nextVolume - curVolume
  382.         local distance = math.abs(diff)
  383.         if distance ~= 0 then
  384.             if sampLen > SamplesPerNote then
  385.                 LenHistogram[SamplesPerNote * 2 + 1] = LenHistogram[SamplesPerNote * 2 + 1] + 1
  386.             else
  387.                 LenHistogram[sampLen] = LenHistogram[sampLen] + 1
  388.             end
  389.             VolHistogram[distance] = VolHistogram[distance] + 1
  390.             sampLen = 0
  391.         end
  392.         sampLen = sampLen + 1
  393.         curVolume = nextVolume
  394.     end
  395.     if sampLen > SamplesPerNote then
  396.         LenHistogram[SamplesPerNote * 2 + 1] = LenHistogram[SamplesPerNote * 2 + 1] + 1
  397.     else
  398.         LenHistogram[sampLen] = LenHistogram[sampLen] + 1
  399.     end
  400.     return VolHistogram, LenHistogram
  401. end
  402.  
  403. function ToFlPCM(MMLFile, PCM, PCMInfo)
  404.     local PCM7 = PreprocessPCM(PCM)
  405.  
  406.     MMLFile:write(VolMacroDefs)
  407.     MMLFile:write(LenMacroDefs)
  408.  
  409.     local sampLen = 0
  410.     local curVolume = PCM7[1]
  411.     MMLFile:write("$wv=@e1,0,0,128,0@5@w8q16l96")
  412.     MMLFile:write(MakeVolCommandAbs(curVolume))
  413.     for sampleIndex = 1, #PCM7 do
  414.         local nextVolume = PCM7[sampleIndex]
  415.         if curVolume ~= nextVolume then
  416.             MMLFile:write(MakeLenCommand(sampLen))
  417.             sampLen = 0
  418.         end
  419.         sampLen = sampLen + 1
  420.         MMLFile:write(MakeVolCommand(curVolume, nextVolume))
  421.         curVolume = nextVolume
  422.     end
  423.     if sampLen > 0 then
  424.         MMLFile:write(MakeLenCommand(sampLen))
  425.     end
  426.     MMLFile:write(";\n")
  427.  
  428.     MMLFile:write("t" .. round(PCMInfo.samplerate * 60 / (SamplesPerNote / 4)) .. "\n")
  429.     MMLFile:write("    $wv;\n")
  430.     MMLFile:write("r384$wv;\n")
  431.     MMLFile:write("r192$wv;\n")
  432.     MMLFile:write("r128$wv;\n")
  433.  
  434.     if ShowHistogram then
  435.         local VolHistogram, LenHistogram = MakeHistogram(PCM7)
  436.         print("#", "vol", "len")
  437.         for index = 1, math.max(127, SamplesPerNote * 2 + 1) do
  438.             print(index,
  439.                 ((index <= 127) and VolHistogram[index] or ""),
  440.                 ((index <= SamplesPerNote * 2 + 1) and LenHistogram[index] or ""),
  441.                 ((index <= SamplesPerNote * 2) and MakeLenCommand(index) or ""))
  442.         end
  443.     end
  444. end
  445.  
  446. -- [ application main ] ---------------
  447.  
  448. local WhichChannel = 1
  449.  
  450. -- parse commandline parameters
  451. local argi = 1
  452. if #arg == 0 then
  453.     ShowUsage()
  454.     return
  455. end
  456. while argi <= #arg and arg[argi]:sub(1, 1) == "-" do
  457.     if arg[argi]:sub(2, 2) == "-" then
  458.         local thisArg = arg[argi]:sub(3)
  459.         argi = argi + 1
  460.  
  461.         if thisArg == "histogram" then
  462.             ShowHistogram = true
  463.         else
  464.             print("Error: unknown option [--" .. thisArg .. "]")
  465.             return
  466.         end
  467.     else
  468.         local argCharIndex = 2
  469.         local thisArg = arg[argi]:sub(2)
  470.         argi = argi + 1
  471.  
  472.         for argCharIndex = 1, #thisArg do
  473.             local thisArgChar = thisArg:sub(argCharIndex, argCharIndex)
  474.             if thisArgChar == "L" then
  475.                 WhichChannel = 1
  476.             elseif thisArgChar == "R" then
  477.                 WhichChannel = 2
  478.             else
  479.                 print("Error: unknown option [-" .. thisArgChar .. "]")
  480.                 return
  481.             end
  482.         end
  483.     end
  484. end
  485. if argi > #arg then
  486.     print("Error: no input files")
  487.     return
  488. elseif argi + 1 < #arg then
  489.     print("Error: too many parameters")
  490.     return
  491. end
  492. local WavFilePath = arg[argi]
  493. local MMLFile = io.stdout
  494. if argi + 1 == #arg then
  495.     MMLFile = io.open(arg[argi + 1], "w")
  496. end
  497.  
  498. local PCM, PCMInfo = ReadWaveFile(WavFilePath)
  499. if WhichChannel < 1 or WhichChannel > #PCM then
  500.     WhichChannel = 1
  501. end
  502. if #PCM == 0 or #PCM[WhichChannel] == 0 then
  503.     print("Error: Unsupported format")
  504.     return
  505. end
  506.  
  507. io.stderr:write(string.format("FlPCM: %d Hz, %d bit, %d channels, %d samples\n",
  508.     PCMInfo.samplerate, PCMInfo.bitwidth, PCMInfo.channels, #PCM[WhichChannel]))
  509.  
  510. ToFlPCM(MMLFile, PCM[WhichChannel], PCMInfo)
  511. MMLFile:close()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement