Standard MIDI file: Lua parsing library

Standard MIDI file, typically known just as "MID", is a standard way to serialize series of MIDI events, which is a protocol used in many music synthesizers to transfer music data: notes being played, effects being applied, etc.

Internally, file consists of a header and series of tracks, every track listing MIDI events with certain header designating time these events are happening.

NOTE: Rarely, MIDI files employ certain stateful compression scheme to avoid storing certain elements of further elements, instead reusing them from events which happened earlier in the stream. Kaitai Struct (as of v0.9) is currently unable to parse these, but files employing this mechanism are relatively rare.

File extension

["mid", "midi", "smf"]

KS implementation details

License: CC0-1.0

References

This page hosts a formal specification of Standard MIDI file using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

Lua source code to parse Standard MIDI file

standard_midi_file.lua

-- This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild
--
-- This file is compatible with Lua 5.3

local class = require("class")
require("kaitaistruct")
local stringstream = require("string_stream")
local enum = require("enum")

require("vlq_base128_be")
-- 
-- Standard MIDI file, typically known just as "MID", is a standard way
-- to serialize series of MIDI events, which is a protocol used in many
-- music synthesizers to transfer music data: notes being played,
-- effects being applied, etc.
-- 
-- Internally, file consists of a header and series of tracks, every
-- track listing MIDI events with certain header designating time these
-- events are happening.
-- 
-- NOTE: Rarely, MIDI files employ certain stateful compression scheme
-- to avoid storing certain elements of further elements, instead
-- reusing them from events which happened earlier in the
-- stream. Kaitai Struct (as of v0.9) is currently unable to parse
-- these, but files employing this mechanism are relatively rare.
StandardMidiFile = class.class(KaitaiStruct)

function StandardMidiFile:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root or self
  self:_read()
end

function StandardMidiFile:_read()
  self.hdr = StandardMidiFile.Header(self._io, self, self._root)
  self.tracks = {}
  for i = 0, self.hdr.num_tracks - 1 do
    self.tracks[i + 1] = StandardMidiFile.Track(self._io, self, self._root)
  end
end


StandardMidiFile.TrackEvents = class.class(KaitaiStruct)

function StandardMidiFile.TrackEvents:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root or self
  self:_read()
end

function StandardMidiFile.TrackEvents:_read()
  self.event = {}
  local i = 0
  while not self._io:is_eof() do
    self.event[i + 1] = StandardMidiFile.TrackEvent(self._io, self, self._root)
    i = i + 1
  end
end


StandardMidiFile.TrackEvent = class.class(KaitaiStruct)

function StandardMidiFile.TrackEvent:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root or self
  self:_read()
end

function StandardMidiFile.TrackEvent:_read()
  self.v_time = VlqBase128Be(self._io)
  self.event_header = self._io:read_u1()
  if self.event_header == 255 then
    self.meta_event_body = StandardMidiFile.MetaEventBody(self._io, self, self._root)
  end
  if self.event_header == 240 then
    self.sysex_body = StandardMidiFile.SysexEventBody(self._io, self, self._root)
  end
  local _on = self.event_type
  if _on == 224 then
    self.event_body = StandardMidiFile.PitchBendEvent(self._io, self, self._root)
  elseif _on == 144 then
    self.event_body = StandardMidiFile.NoteOnEvent(self._io, self, self._root)
  elseif _on == 208 then
    self.event_body = StandardMidiFile.ChannelPressureEvent(self._io, self, self._root)
  elseif _on == 192 then
    self.event_body = StandardMidiFile.ProgramChangeEvent(self._io, self, self._root)
  elseif _on == 160 then
    self.event_body = StandardMidiFile.PolyphonicPressureEvent(self._io, self, self._root)
  elseif _on == 176 then
    self.event_body = StandardMidiFile.ControllerEvent(self._io, self, self._root)
  elseif _on == 128 then
    self.event_body = StandardMidiFile.NoteOffEvent(self._io, self, self._root)
  end
end

StandardMidiFile.TrackEvent.property.event_type = {}
function StandardMidiFile.TrackEvent.property.event_type:get()
  if self._m_event_type ~= nil then
    return self._m_event_type
  end

  self._m_event_type = (self.event_header & 240)
  return self._m_event_type
end

StandardMidiFile.TrackEvent.property.channel = {}
function StandardMidiFile.TrackEvent.property.channel:get()
  if self._m_channel ~= nil then
    return self._m_channel
  end

  if self.event_type ~= 240 then
    self._m_channel = (self.event_header & 15)
  end
  return self._m_channel
end


StandardMidiFile.PitchBendEvent = class.class(KaitaiStruct)

function StandardMidiFile.PitchBendEvent:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root or self
  self:_read()
end

function StandardMidiFile.PitchBendEvent:_read()
  self.b1 = self._io:read_u1()
  self.b2 = self._io:read_u1()
end

StandardMidiFile.PitchBendEvent.property.bend_value = {}
function StandardMidiFile.PitchBendEvent.property.bend_value:get()
  if self._m_bend_value ~= nil then
    return self._m_bend_value
  end

  self._m_bend_value = (((self.b2 << 7) + self.b1) - 16384)
  return self._m_bend_value
end

StandardMidiFile.PitchBendEvent.property.adj_bend_value = {}
function StandardMidiFile.PitchBendEvent.property.adj_bend_value:get()
  if self._m_adj_bend_value ~= nil then
    return self._m_adj_bend_value
  end

  self._m_adj_bend_value = (self.bend_value - 16384)
  return self._m_adj_bend_value
end


StandardMidiFile.ProgramChangeEvent = class.class(KaitaiStruct)

function StandardMidiFile.ProgramChangeEvent:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root or self
  self:_read()
end

function StandardMidiFile.ProgramChangeEvent:_read()
  self.program = self._io:read_u1()
end


StandardMidiFile.NoteOnEvent = class.class(KaitaiStruct)

function StandardMidiFile.NoteOnEvent:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root or self
  self:_read()
end

function StandardMidiFile.NoteOnEvent:_read()
  self.note = self._io:read_u1()
  self.velocity = self._io:read_u1()
end


StandardMidiFile.PolyphonicPressureEvent = class.class(KaitaiStruct)

function StandardMidiFile.PolyphonicPressureEvent:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root or self
  self:_read()
end

function StandardMidiFile.PolyphonicPressureEvent:_read()
  self.note = self._io:read_u1()
  self.pressure = self._io:read_u1()
end


StandardMidiFile.Track = class.class(KaitaiStruct)

function StandardMidiFile.Track:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root or self
  self:_read()
end

function StandardMidiFile.Track:_read()
  self.magic = self._io:read_bytes(4)
  if not(self.magic == "\077\084\114\107") then
    error("not equal, expected " ..  "\077\084\114\107" .. ", but got " .. self.magic)
  end
  self.len_events = self._io:read_u4be()
  self._raw_events = self._io:read_bytes(self.len_events)
  local _io = KaitaiStream(stringstream(self._raw_events))
  self.events = StandardMidiFile.TrackEvents(_io, self, self._root)
end


StandardMidiFile.MetaEventBody = class.class(KaitaiStruct)

StandardMidiFile.MetaEventBody.MetaTypeEnum = enum.Enum {
  sequence_number = 0,
  text_event = 1,
  copyright = 2,
  sequence_track_name = 3,
  instrument_name = 4,
  lyric_text = 5,
  marker_text = 6,
  cue_point = 7,
  midi_channel_prefix_assignment = 32,
  end_of_track = 47,
  tempo = 81,
  smpte_offset = 84,
  time_signature = 88,
  key_signature = 89,
  sequencer_specific_event = 127,
}

function StandardMidiFile.MetaEventBody:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root or self
  self:_read()
end

function StandardMidiFile.MetaEventBody:_read()
  self.meta_type = StandardMidiFile.MetaEventBody.MetaTypeEnum(self._io:read_u1())
  self.len = VlqBase128Be(self._io)
  self.body = self._io:read_bytes(self.len.value)
end


StandardMidiFile.ControllerEvent = class.class(KaitaiStruct)

function StandardMidiFile.ControllerEvent:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root or self
  self:_read()
end

function StandardMidiFile.ControllerEvent:_read()
  self.controller = self._io:read_u1()
  self.value = self._io:read_u1()
end


StandardMidiFile.Header = class.class(KaitaiStruct)

function StandardMidiFile.Header:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root or self
  self:_read()
end

function StandardMidiFile.Header:_read()
  self.magic = self._io:read_bytes(4)
  if not(self.magic == "\077\084\104\100") then
    error("not equal, expected " ..  "\077\084\104\100" .. ", but got " .. self.magic)
  end
  self.len_header = self._io:read_u4be()
  self.format = self._io:read_u2be()
  self.num_tracks = self._io:read_u2be()
  self.division = self._io:read_s2be()
end


StandardMidiFile.SysexEventBody = class.class(KaitaiStruct)

function StandardMidiFile.SysexEventBody:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root or self
  self:_read()
end

function StandardMidiFile.SysexEventBody:_read()
  self.len = VlqBase128Be(self._io)
  self.data = self._io:read_bytes(self.len.value)
end


StandardMidiFile.NoteOffEvent = class.class(KaitaiStruct)

function StandardMidiFile.NoteOffEvent:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root or self
  self:_read()
end

function StandardMidiFile.NoteOffEvent:_read()
  self.note = self._io:read_u1()
  self.velocity = self._io:read_u1()
end


StandardMidiFile.ChannelPressureEvent = class.class(KaitaiStruct)

function StandardMidiFile.ChannelPressureEvent:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root or self
  self:_read()
end

function StandardMidiFile.ChannelPressureEvent:_read()
  self.pressure = self._io:read_u1()
end