glitchdetector icon

MIDI Parser

glitchdetector | PRO | 10/10/18 07:57:40 PM UTC | 0 ⭐ | 524 👁️ | Never ⏰ | []
Lua |

7.35 KB

|

None

|

0 👍

/

0 👎

-- Usage:
-- midi = midiParser( filePath )
 
local midiParser = function( _path )
 
  local ret = {}
 
  ------------------
  -- prepare file --
  ------------------
 
  if _path == nil then
    error( 'path is not defined' )
  end
 
  local file = io.open( _path, 'rb' )
  if file == nil then
        error( 'not found: ' .. _path )
    end
 
  local midi = file:read( '*all' )
  midi = string.gsub( midi, '\r\n', '\n' )
 
  --------------------
  -- some functions --
  --------------------
 
  local byteArray = function( _start, _length )
    local retArray = {}
    for i = 1, _length do
      retArray[ i ] = string.byte( midi, i + _start - 1 )
    end
    return retArray
  end
 
  local bytesToNumber = function( _start, _length )
    local retNumber = 0
    for i = 1, _length do
      retNumber = retNumber + string.byte( midi, i + _start - 1 ) * math.pow( 256, _length - i )
    end
    return retNumber
  end
 
  local vlq = function( _start ) -- Variable-length quantity
    local retNumber = 0
    local head = 0
    local byte = 0
    repeat
      byte = string.byte( midi, _start + head )
      retNumber = retNumber * 128 + ( byte - math.floor( byte / 128 ) * 128 )
      head = head + 1
    until math.floor( byte / 128 ) ~= 1
    return retNumber, head
  end
 
  local isSameTable = function( _a, _b )
    for i, v in ipairs( _a ) do
      if _a[ i ] ~= _b[ i ] then
        return false
      end
    end
    return true
  end
 
  ------------------
  -- check format --
  ------------------
 
  local head = 1
 
  if not isSameTable( byteArray( head, 4 ), { 77, 84, 104, 100 } ) then
    error( 'input file seems not to be a .mid file' )
  end
  head = head + 4 -- header chunk magic number
  head = head + 4 -- header chunk length
 
  ret.format = bytesToNumber( head, 2 )
 
  if not ( ret.format == 0 or ret.format == 1 ) then
    error( 'not supported such format of .mid' )
  end
  head = head + 2 -- format
 
  head = head + 2 -- trackCount
 
  ret.timebase = bytesToNumber( head, 2 )
  head = head + 2 -- timeBase
 
  ------------------------
  -- fight against .mid --
  ------------------------
 
  ret.tracks = {}
 
  while head < string.len( midi ) do
 
    if not isSameTable( byteArray( head, 4 ), { 77, 84, 114, 107 } ) then -- if chunk is not track chunk
 
      head = head + 4 -- unknown chunk magic number
      head = head + 4 + bytesToNumber( head, 4 ) -- chunk length + chunk data
 
    else
 
      head = head + 4 -- track chunk magic number
 
      local chunkLength = bytesToNumber( head, 4 )
      head = head + 4 -- chunk length
      local chunkStart = head
 
      local track = {}
      track.messages = {}
      table.insert( ret.tracks, track )
 
      local status = 0
      while head < chunkStart + chunkLength do
 
        local deltaTime, deltaHead = vlq( head ) -- timing
        head = head + deltaHead
 
        local tempStatus = byteArray( head, 1 )[ 1 ]
 
        if math.floor( tempStatus / 128 ) == 1 then -- event, running status
          head = head + 1
          status = tempStatus
        end
 
        local type = math.floor( status / 16 )
        local channel = status - type * 16
 
        if type == 8 then -- note off
          local data = byteArray( head, 2 )
          head = head + 2
 
          table.insert( track.messages, {
            time = deltaTime,
            type = 'off',
            channel = channel,
            number = data[ 1 ],
            velocity = data[ 2 ]
          } )
 
        elseif type == 9 then -- note on
          local data = byteArray( head, 2 )
          head = head + 2
 
          table.insert( track.messages, {
            time = deltaTime,
            type = 'on',
            channel = channel,
            number = data[ 1 ],
            velocity = data[ 2 ]
          } )
 
        elseif type == 10 then -- polyphonic keypressure
          head = head + 2
 
        elseif type == 11 then -- control change
          head = head + 2
 
        elseif type == 12 then -- program change
          head = head + 1
 
        elseif type == 13 then -- channel pressure
          head = head + 1
 
        elseif type == 14 then -- pitch bend
          head = head + 2
 
        elseif status == 255 then -- meta event
          local metaType = byteArray( head, 1 )[ 1 ]
          head = head + 1
          local metaLength, metaHead = vlq( head )
 
          if metaType == 3 then -- track name
            head = head + metaHead
            track.name = string.sub( midi, head, head + metaLength - 1 )
            head = head + metaLength
 
            table.insert( track.messages, {
              time = deltaTime,
              type = 'meta',
              meta = 'Track Name',
              text = track.name
            } )
 
          elseif metaType == 4 then -- instrument name
            head = head + metaHead
            track.instrument = string.sub( midi, head, head + metaLength - 1 )
            head = head + metaLength
 
            table.insert( track.messages, {
              time = deltaTime,
              type = 'meta',
              meta = 'Instrument Name',
              text = track.instrument
            } )
 
          elseif metaType == 5 then -- lyric
            head = head + metaHead
            track.lyric = string.sub( midi, head, head + metaLength - 1 )
            head = head + metaLength
 
            table.insert( track.messages, {
              time = deltaTime,
              type = 'meta',
              meta = 'Lyric',
              text = track.lyric
            } )
 
          elseif metaType == 47 then -- end of track
            head = head + 1
 
            table.insert( track.messages, {
              time = deltaTime,
              type = 'meta',
              meta = 'End of Track'
            } )
 
            break
 
          elseif metaType == 81 then -- tempo
            head = head + 1
 
            local micros = bytesToNumber( head, 3 )
            head = head + 3
 
            table.insert( track.messages, {
              time = deltaTime,
              type = 'meta',
              meta = 'Set Tempo',
              tempo = micros
            } )
 
          elseif metaType == 88 then -- time signature
            head = head + 1
 
            local sig = byteArray( head, 4 )
            head = head + 4
 
            table.insert( track.messages, {
              time = deltaTime,
              type = 'meta',
              meta = 'Time Signature',
              signature = sig
            } )
 
          elseif metaType == 89 then -- key signature
            head = head + 1
 
            local sig = byteArray( head, 2 )
            head = head + 2
 
            table.insert( track.messages, {
              time = deltaTime,
              type = 'meta',
              meta = 'Key Signature',
              signature = sig
            } )
 
          else -- comment
            head = head + metaHead
            local text = string.sub( midi, head, head + metaLength - 1 )
            head = head + metaLength
 
            table.insert( track.messages, {
              time = deltaTime,
              type = 'meta',
              meta = 'Unknown Text: ' .. event[ 2 ],
              text = text
            } )
 
          end
 
        end
 
      end
 
    end
 
  end
 
  return ret
 
end
 
return midiParser

Comments