Guest User

midi.lua

a guest
May 1st, 2015
394
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. local component = require("component")
  2. local computer = require("computer")
  3. local shell = require("shell")
  4.  
  5. local args, options = shell.parse(...)
  6. if #args < 1 then
  7.   print("Usage: midi [-i] <filename> [track1 [track2 [...]]]")
  8.   print("Where the tracks are the numbers of the tracks to actually play.")
  9.   print(" -i: only parse and show track info, don't play.")
  10.   return
  11. end
  12.  
  13. -- Implements the essentials for MIDI file parsing, references:
  14. -- http://www.recordingblogs.com/sa/tabid/88/Default.aspx?topic=Musical+Instrument+Digital+Interface+(MIDI)
  15. -- http://www.sonicspot.com/guide/midifiles.html
  16.  
  17. local enabledTracks = {n=0}
  18. for i = 2, #args do
  19.   enabledTracks[tonumber(args[i])] = true
  20.   enabledTracks.n = enabledTracks.n + 1
  21. end
  22.  
  23. local instruments = {}
  24. for address in component.list("note_block") do
  25.   table.insert(instruments, function(note)
  26.     -- 60 = C in MIDI, 6 = C in Minecraft
  27.     component.invoke(address, "trigger", (note + 6 - 60) % 24 + 1)
  28.   end)
  29. end
  30. if #instruments == 0 then
  31.   print("No note blocks found, falling back to built-in speaker.")
  32.   instruments[1] = function(note)
  33.     pcall(computer.beep, math.pow(2, (note - 69) / 12) * 440, 0.05)
  34.     return true -- only one event per tick
  35.   end
  36. else
  37.   print("Using " .. #instruments .. " note blocks.")
  38. end
  39.  
  40. local filename = shell.resolve(args[1])
  41. local f, reason = io.open(filename, "rb")
  42. if not f then
  43.   print(reason)
  44.   return
  45. end
  46.  
  47. local function parseVarInt(s, bits) -- parses multiple bytes as an integer
  48.   if not s then
  49.     error("error parsing file")
  50.   end
  51.   bits = bits or 8
  52.   local mask = bit32.rshift(0xFF, 8 - bits)
  53.   local num = 0
  54.   for i = 1, s:len() do
  55.     num = num + bit32.lshift(bit32.band(s:byte(i), mask), (s:len() - i) * bits)
  56.   end
  57.   return num
  58. end
  59.  
  60. local function readChunkInfo() -- reads chunk header info
  61.   local id = f:read(4)
  62.   if not id then
  63.     return
  64.   end
  65.   return id, parseVarInt(f:read(4))
  66. end
  67.  
  68. -- Read the file header and with if file information.
  69. local id, size = readChunkInfo()
  70. if id ~= "MThd" or size ~= 6 then
  71.   print("error parsing header (" .. id .. "/" .. size .. ")")
  72.   return
  73. end
  74.  
  75. local format = parseVarInt(f:read(2))
  76. local tracks = parseVarInt(f:read(2))
  77. local delta = parseVarInt(f:read(2))
  78.  
  79. if format < 0 or format > 2 then
  80.   print("unknown format")
  81.   return
  82. end
  83.  
  84. local formatName = ({"single", "synchronous", "asynchronous"})[format + 1]
  85. print(string.format("Found %d %s tracks.", tracks, formatName))
  86.  
  87. if format == 2 then
  88.   print("Sorry, asynchronous tracks are not supported.")
  89.   return
  90. end
  91.  
  92. -- Figure out our time system and prepare accordingly.
  93. local time = {division = bit32.band(0x8000, delta) == 0 and "tpb" or "fps"}
  94. if time.division == "tpb" then
  95.   time.tpb = bit32.band(0x7FFF, delta)
  96.   time.mspb = 500000
  97.   function time.tick()
  98.     return time.mspb / time.tpb
  99.   end
  100.   print(string.format("Time division is in %d ticks per beat.", time.tpb))
  101. else
  102.   time.fps = bit32.band(0x7F00, delta)
  103.   time.tpf = bit32.band(0x00FF, delta)
  104.   function time.tick()
  105.     return 1000000 / (time.fps * time.tpf)
  106.   end
  107.   print(string.format("Time division is in %d frames per second with %d ticks per frame.", time.fps, time.tpf))
  108. end
  109.  
  110. -- Parse all track chunks.
  111. local totalOffset = 0
  112. local totalLength = 0
  113. local tracks = {}
  114. while true do
  115.   local id, size = readChunkInfo()
  116.   if not id then
  117.     break
  118.   end
  119.   if id == "MTrk" then
  120.     local track = {}
  121.     local cursor = 0
  122.     local start, offset = f:seek(), 0
  123.     local inSysEx = false
  124.     local running = 0
  125.  
  126.     local function read(n)
  127.       n = n or 1
  128.       if n > 0 then
  129.         offset = offset + n
  130.         return f:read(n)
  131.       end
  132.     end
  133.     local function readVariableLength()
  134.       local total = ""
  135.       for i = 1, math.huge do
  136.         local part = read()
  137.         total = total .. part
  138.         if bit32.band(0x80, part:byte(1)) == 0 then
  139.           return parseVarInt(total, 7)
  140.         end
  141.       end
  142.     end
  143.     local function parseVoiceMessage(event)
  144.       local channel = bit32.band(0xF, event)
  145.       local note = parseVarInt(read())
  146.       local velocity = parseVarInt(read())
  147.       return channel, note, velocity
  148.     end
  149.  
  150.     while offset < size do
  151.       cursor = cursor + readVariableLength()
  152.       totalLength = math.max(totalLength, cursor)
  153.       local test = parseVarInt(read())
  154.       if inSysEx and test ~= 0xF7 then
  155.         error("corrupt file: could not find continuation of divided sysex event")
  156.       end
  157.       local event
  158.       if bit32.band(test, 0x80) == 0 then
  159.         if running == 0 then
  160.           error("corrupt file: invalid running status")
  161.         end
  162.         f.bufferRead = string.char(test) .. f.bufferRead
  163.         offset = offset - 1
  164.         event = running
  165.       else
  166.         event = test
  167.         if test < 0xF0 then
  168.           running = test
  169.         end
  170.       end
  171.       local status = bit32.band(0xF0, event)
  172.       if status == 0x80 then -- Note off.
  173.         parseVoiceMessage(event) -- not handled
  174.       elseif status == 0x90 then -- Note on.
  175.         local channel, note, velocity = parseVoiceMessage(event)
  176.         track[cursor] = {channel, note, velocity}
  177.       elseif status == 0xA0 then -- Aftertouch / key pressure
  178.         parseVoiceMessage(event) -- not handled
  179.       elseif status == 0xB0 then -- Controller
  180.         parseVoiceMessage(event) -- not handled
  181.       elseif status == 0xC0 then -- Program change
  182.         parseVarInt(read()) -- not handled
  183.       elseif status == 0xD0 then -- Channel pressure
  184.         parseVarInt(read()) -- not handled
  185.       elseif status == 0xE0 then -- Pitch / modulation wheel
  186.         parseVarInt(read(2), 7) -- not handled
  187.       elseif event == 0xF0 then -- System exclusive event
  188.         local length = readVariableLength()
  189.         if length > 0 then
  190.           read(length - 1)
  191.           inSysEx = read(1):byte(1) ~= 0xF7
  192.         end
  193.       elseif event == 0xF1 then -- MIDI time code quarter frame
  194.         parseVarInt(read()) -- not handled
  195.       elseif event == 0xF2 then -- Song position pointer
  196.         parseVarInt(read(2), 7) -- not handled
  197.       elseif event == 0xF3 then -- Song select
  198.         parseVarInt(read(2), 7) -- not handled
  199.       elseif event == 0xF7 then -- Divided system exclusive event
  200.         local length = readVariableLength()
  201.         if length > 0 then
  202.           read(length - 1)
  203.           inSysEx = read(1):byte(1) ~= 0xF7
  204.         else
  205.           inSysEx = false
  206.         end
  207.       elseif event >= 0xF8 and event <= 0xFE then -- System real-time event
  208.         -- not handled
  209.       elseif event == 0xFF then
  210.         -- Meta message.
  211.         local metaType = parseVarInt(read())
  212.         local length = parseVarInt(read())
  213.         local data = read(length)
  214.  
  215.         if metaType == 0x00 then -- Sequence number
  216.           track.sequence = parseVarInt(data)
  217.         elseif metaType == 0x01 then -- Text event
  218.         elseif metaType == 0x02 then -- Copyright notice
  219.         elseif metaType == 0x03 then -- Sequence / track name
  220.           track.name = data
  221.         elseif metaType == 0x04 then -- Instrument name
  222.           track.instrument = data
  223.         elseif metaType == 0x05 then -- Lyric text
  224.         elseif metaType == 0x06 then -- Marker text
  225.         elseif metaType == 0x07 then -- Cue point
  226.         elseif metaType == 0x20 then -- Channel prefix assignment
  227.         elseif metaType == 0x2F then -- End of track
  228.           track.eot = cursor
  229.         elseif metaType == 0x51 then -- Tempo setting
  230.           track[cursor] = parseVarInt(data)
  231.         elseif metaType == 0x54 then -- SMPTE offset
  232.         elseif metaType == 0x58 then -- Time signature
  233.         elseif metaType == 0x59 then -- Key signature
  234.         elseif metaType == 0x7F then -- Sequencer specific event
  235.         end
  236.       else
  237.         f:seek("cur", -9)
  238.         local area = f:read(16)
  239.         local dump = ""
  240.         for i = 1, area:len() do
  241.           dump = dump .. string.format(" %02X", area:byte(i))
  242.           if i % 4 == 0 then
  243.             dump = dump .. "\n"
  244.           end
  245.         end
  246.         error(string.format("midi file contains unhandled event types:\n0x%X at offset %d/%d\ndump of the surrounding area:\n%s", event, offset, size, dump))
  247.       end
  248.     end
  249.     local delta = size - offset
  250.     if delta ~= 0 then
  251.       f:seek("cur", delta)
  252.     end
  253.     totalOffset = totalOffset + size
  254.     table.insert(tracks, track)
  255.   else
  256.     print(string.format("Encountered unknown chunk type %s, skipping.", id))
  257.     f:seek("cur", size)
  258.   end
  259. end
  260.  
  261. f:close()
  262.  
  263. if options.i then
  264.   print(string.format("Found %d tracks, total length is %d ticks.", #tracks, totalLength))
  265.   for i, track in ipairs(tracks) do
  266.     if track.name then
  267.       print(string.format("#%d: %s", i, track.name))
  268.     end
  269.   end
  270.   return
  271. end
  272.  
  273. local removed = 0
  274. if enabledTracks.n > 0 then
  275.   for i = #tracks, 1, -1 do
  276.     if not enabledTracks[i] then
  277.       table.remove(tracks, i)
  278.       removed = removed + 1
  279.     end
  280.   end
  281. end
  282. print("Playing " .. #tracks .. " tracks:")
  283. for _, track in ipairs(tracks) do
  284.   if track.name then
  285.     print(string.format("%s", track.name))
  286.   end
  287. end
  288.  
  289. local channels = {n=0}
  290. local lastTick, lastTime = 0, computer.uptime()
  291. for tick = 1, totalLength do
  292.   local hasEvent = false
  293.   for _, track in ipairs(tracks) do
  294.     if track[tick] then
  295.       hasEvent = true
  296.       break
  297.     end
  298.   end
  299.   if hasEvent then
  300.     local delay = (tick - lastTick) * time.tick() / 1000000
  301.     if delay > 0.05 or computer.uptime() then
  302.       os.sleep(delay)
  303.     else
  304.       -- Busy idle otherwise, because a sleep will take up to 50ms.
  305.       local begin = os.clock()
  306.       while os.clock() - begin < delay do end
  307.     end
  308.     lastTick = tick
  309.     lastTime = computer.uptime()
  310.     for _, track in ipairs(tracks) do
  311.       local event = track[tick]
  312.       if event then
  313.         if type(event) == "number" then
  314.           time.mspb = event
  315.         elseif type(event) == "table" then
  316.           local channel, note, velocity = table.unpack(event)
  317.           local instrument
  318.           if not channels[channel] then
  319.             channels.n = channels.n + 1
  320.             channels[channel] = instruments[1 + (channels.n % #instruments)]
  321.           end
  322.           if channels[channel](note) then
  323.             break
  324.           end
  325.         end
  326.       end
  327.     end
  328.   end
  329. end
RAW Paste Data