Standard MIDI file: Perl 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.

Perl source code to parse Standard MIDI file

StandardMidiFile.pm

# This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

use strict;
use warnings;
use IO::KaitaiStruct 0.009_000;
use VlqBase128Be;

########################################################################
package StandardMidiFile;

our @ISA = 'IO::KaitaiStruct::Struct';

sub from_file {
    my ($class, $filename) = @_;
    my $fd;

    open($fd, '<', $filename) or return undef;
    binmode($fd);
    return new($class, IO::KaitaiStruct::Stream->new($fd));
}

sub new {
    my ($class, $_io, $_parent, $_root) = @_;
    my $self = IO::KaitaiStruct::Struct->new($_io);

    bless $self, $class;
    $self->{_parent} = $_parent;
    $self->{_root} = $_root || $self;;

    $self->_read();

    return $self;
}

sub _read {
    my ($self) = @_;

    $self->{hdr} = StandardMidiFile::Header->new($self->{_io}, $self, $self->{_root});
    $self->{tracks} = ();
    my $n_tracks = $self->hdr()->num_tracks();
    for (my $i = 0; $i < $n_tracks; $i++) {
        push @{$self->{tracks}}, StandardMidiFile::Track->new($self->{_io}, $self, $self->{_root});
    }
}

sub hdr {
    my ($self) = @_;
    return $self->{hdr};
}

sub tracks {
    my ($self) = @_;
    return $self->{tracks};
}

########################################################################
package StandardMidiFile::TrackEvents;

our @ISA = 'IO::KaitaiStruct::Struct';

sub from_file {
    my ($class, $filename) = @_;
    my $fd;

    open($fd, '<', $filename) or return undef;
    binmode($fd);
    return new($class, IO::KaitaiStruct::Stream->new($fd));
}

sub new {
    my ($class, $_io, $_parent, $_root) = @_;
    my $self = IO::KaitaiStruct::Struct->new($_io);

    bless $self, $class;
    $self->{_parent} = $_parent;
    $self->{_root} = $_root || $self;;

    $self->_read();

    return $self;
}

sub _read {
    my ($self) = @_;

    $self->{event} = ();
    while (!$self->{_io}->is_eof()) {
        push @{$self->{event}}, StandardMidiFile::TrackEvent->new($self->{_io}, $self, $self->{_root});
    }
}

sub event {
    my ($self) = @_;
    return $self->{event};
}

########################################################################
package StandardMidiFile::TrackEvent;

our @ISA = 'IO::KaitaiStruct::Struct';

sub from_file {
    my ($class, $filename) = @_;
    my $fd;

    open($fd, '<', $filename) or return undef;
    binmode($fd);
    return new($class, IO::KaitaiStruct::Stream->new($fd));
}

sub new {
    my ($class, $_io, $_parent, $_root) = @_;
    my $self = IO::KaitaiStruct::Struct->new($_io);

    bless $self, $class;
    $self->{_parent} = $_parent;
    $self->{_root} = $_root || $self;;

    $self->_read();

    return $self;
}

sub _read {
    my ($self) = @_;

    $self->{v_time} = VlqBase128Be->new($self->{_io});
    $self->{event_header} = $self->{_io}->read_u1();
    if ($self->event_header() == 255) {
        $self->{meta_event_body} = StandardMidiFile::MetaEventBody->new($self->{_io}, $self, $self->{_root});
    }
    if ($self->event_header() == 240) {
        $self->{sysex_body} = StandardMidiFile::SysexEventBody->new($self->{_io}, $self, $self->{_root});
    }
    my $_on = $self->event_type();
    if ($_on == 224) {
        $self->{event_body} = StandardMidiFile::PitchBendEvent->new($self->{_io}, $self, $self->{_root});
    }
    elsif ($_on == 144) {
        $self->{event_body} = StandardMidiFile::NoteOnEvent->new($self->{_io}, $self, $self->{_root});
    }
    elsif ($_on == 208) {
        $self->{event_body} = StandardMidiFile::ChannelPressureEvent->new($self->{_io}, $self, $self->{_root});
    }
    elsif ($_on == 192) {
        $self->{event_body} = StandardMidiFile::ProgramChangeEvent->new($self->{_io}, $self, $self->{_root});
    }
    elsif ($_on == 160) {
        $self->{event_body} = StandardMidiFile::PolyphonicPressureEvent->new($self->{_io}, $self, $self->{_root});
    }
    elsif ($_on == 176) {
        $self->{event_body} = StandardMidiFile::ControllerEvent->new($self->{_io}, $self, $self->{_root});
    }
    elsif ($_on == 128) {
        $self->{event_body} = StandardMidiFile::NoteOffEvent->new($self->{_io}, $self, $self->{_root});
    }
}

sub event_type {
    my ($self) = @_;
    return $self->{event_type} if ($self->{event_type});
    $self->{event_type} = ($self->event_header() & 240);
    return $self->{event_type};
}

sub channel {
    my ($self) = @_;
    return $self->{channel} if ($self->{channel});
    if ($self->event_type() != 240) {
        $self->{channel} = ($self->event_header() & 15);
    }
    return $self->{channel};
}

sub v_time {
    my ($self) = @_;
    return $self->{v_time};
}

sub event_header {
    my ($self) = @_;
    return $self->{event_header};
}

sub meta_event_body {
    my ($self) = @_;
    return $self->{meta_event_body};
}

sub sysex_body {
    my ($self) = @_;
    return $self->{sysex_body};
}

sub event_body {
    my ($self) = @_;
    return $self->{event_body};
}

########################################################################
package StandardMidiFile::PitchBendEvent;

our @ISA = 'IO::KaitaiStruct::Struct';

sub from_file {
    my ($class, $filename) = @_;
    my $fd;

    open($fd, '<', $filename) or return undef;
    binmode($fd);
    return new($class, IO::KaitaiStruct::Stream->new($fd));
}

sub new {
    my ($class, $_io, $_parent, $_root) = @_;
    my $self = IO::KaitaiStruct::Struct->new($_io);

    bless $self, $class;
    $self->{_parent} = $_parent;
    $self->{_root} = $_root || $self;;

    $self->_read();

    return $self;
}

sub _read {
    my ($self) = @_;

    $self->{b1} = $self->{_io}->read_u1();
    $self->{b2} = $self->{_io}->read_u1();
}

sub bend_value {
    my ($self) = @_;
    return $self->{bend_value} if ($self->{bend_value});
    $self->{bend_value} = ((($self->b2() << 7) + $self->b1()) - 16384);
    return $self->{bend_value};
}

sub adj_bend_value {
    my ($self) = @_;
    return $self->{adj_bend_value} if ($self->{adj_bend_value});
    $self->{adj_bend_value} = ($self->bend_value() - 16384);
    return $self->{adj_bend_value};
}

sub b1 {
    my ($self) = @_;
    return $self->{b1};
}

sub b2 {
    my ($self) = @_;
    return $self->{b2};
}

########################################################################
package StandardMidiFile::ProgramChangeEvent;

our @ISA = 'IO::KaitaiStruct::Struct';

sub from_file {
    my ($class, $filename) = @_;
    my $fd;

    open($fd, '<', $filename) or return undef;
    binmode($fd);
    return new($class, IO::KaitaiStruct::Stream->new($fd));
}

sub new {
    my ($class, $_io, $_parent, $_root) = @_;
    my $self = IO::KaitaiStruct::Struct->new($_io);

    bless $self, $class;
    $self->{_parent} = $_parent;
    $self->{_root} = $_root || $self;;

    $self->_read();

    return $self;
}

sub _read {
    my ($self) = @_;

    $self->{program} = $self->{_io}->read_u1();
}

sub program {
    my ($self) = @_;
    return $self->{program};
}

########################################################################
package StandardMidiFile::NoteOnEvent;

our @ISA = 'IO::KaitaiStruct::Struct';

sub from_file {
    my ($class, $filename) = @_;
    my $fd;

    open($fd, '<', $filename) or return undef;
    binmode($fd);
    return new($class, IO::KaitaiStruct::Stream->new($fd));
}

sub new {
    my ($class, $_io, $_parent, $_root) = @_;
    my $self = IO::KaitaiStruct::Struct->new($_io);

    bless $self, $class;
    $self->{_parent} = $_parent;
    $self->{_root} = $_root || $self;;

    $self->_read();

    return $self;
}

sub _read {
    my ($self) = @_;

    $self->{note} = $self->{_io}->read_u1();
    $self->{velocity} = $self->{_io}->read_u1();
}

sub note {
    my ($self) = @_;
    return $self->{note};
}

sub velocity {
    my ($self) = @_;
    return $self->{velocity};
}

########################################################################
package StandardMidiFile::PolyphonicPressureEvent;

our @ISA = 'IO::KaitaiStruct::Struct';

sub from_file {
    my ($class, $filename) = @_;
    my $fd;

    open($fd, '<', $filename) or return undef;
    binmode($fd);
    return new($class, IO::KaitaiStruct::Stream->new($fd));
}

sub new {
    my ($class, $_io, $_parent, $_root) = @_;
    my $self = IO::KaitaiStruct::Struct->new($_io);

    bless $self, $class;
    $self->{_parent} = $_parent;
    $self->{_root} = $_root || $self;;

    $self->_read();

    return $self;
}

sub _read {
    my ($self) = @_;

    $self->{note} = $self->{_io}->read_u1();
    $self->{pressure} = $self->{_io}->read_u1();
}

sub note {
    my ($self) = @_;
    return $self->{note};
}

sub pressure {
    my ($self) = @_;
    return $self->{pressure};
}

########################################################################
package StandardMidiFile::Track;

our @ISA = 'IO::KaitaiStruct::Struct';

sub from_file {
    my ($class, $filename) = @_;
    my $fd;

    open($fd, '<', $filename) or return undef;
    binmode($fd);
    return new($class, IO::KaitaiStruct::Stream->new($fd));
}

sub new {
    my ($class, $_io, $_parent, $_root) = @_;
    my $self = IO::KaitaiStruct::Struct->new($_io);

    bless $self, $class;
    $self->{_parent} = $_parent;
    $self->{_root} = $_root || $self;;

    $self->_read();

    return $self;
}

sub _read {
    my ($self) = @_;

    $self->{magic} = $self->{_io}->read_bytes(4);
    $self->{len_events} = $self->{_io}->read_u4be();
    $self->{_raw_events} = $self->{_io}->read_bytes($self->len_events());
    my $io__raw_events = IO::KaitaiStruct::Stream->new($self->{_raw_events});
    $self->{events} = StandardMidiFile::TrackEvents->new($io__raw_events, $self, $self->{_root});
}

sub magic {
    my ($self) = @_;
    return $self->{magic};
}

sub len_events {
    my ($self) = @_;
    return $self->{len_events};
}

sub events {
    my ($self) = @_;
    return $self->{events};
}

sub _raw_events {
    my ($self) = @_;
    return $self->{_raw_events};
}

########################################################################
package StandardMidiFile::MetaEventBody;

our @ISA = 'IO::KaitaiStruct::Struct';

sub from_file {
    my ($class, $filename) = @_;
    my $fd;

    open($fd, '<', $filename) or return undef;
    binmode($fd);
    return new($class, IO::KaitaiStruct::Stream->new($fd));
}

our $META_TYPE_ENUM_SEQUENCE_NUMBER = 0;
our $META_TYPE_ENUM_TEXT_EVENT = 1;
our $META_TYPE_ENUM_COPYRIGHT = 2;
our $META_TYPE_ENUM_SEQUENCE_TRACK_NAME = 3;
our $META_TYPE_ENUM_INSTRUMENT_NAME = 4;
our $META_TYPE_ENUM_LYRIC_TEXT = 5;
our $META_TYPE_ENUM_MARKER_TEXT = 6;
our $META_TYPE_ENUM_CUE_POINT = 7;
our $META_TYPE_ENUM_MIDI_CHANNEL_PREFIX_ASSIGNMENT = 32;
our $META_TYPE_ENUM_END_OF_TRACK = 47;
our $META_TYPE_ENUM_TEMPO = 81;
our $META_TYPE_ENUM_SMPTE_OFFSET = 84;
our $META_TYPE_ENUM_TIME_SIGNATURE = 88;
our $META_TYPE_ENUM_KEY_SIGNATURE = 89;
our $META_TYPE_ENUM_SEQUENCER_SPECIFIC_EVENT = 127;

sub new {
    my ($class, $_io, $_parent, $_root) = @_;
    my $self = IO::KaitaiStruct::Struct->new($_io);

    bless $self, $class;
    $self->{_parent} = $_parent;
    $self->{_root} = $_root || $self;;

    $self->_read();

    return $self;
}

sub _read {
    my ($self) = @_;

    $self->{meta_type} = $self->{_io}->read_u1();
    $self->{len} = VlqBase128Be->new($self->{_io});
    $self->{body} = $self->{_io}->read_bytes($self->len()->value());
}

sub meta_type {
    my ($self) = @_;
    return $self->{meta_type};
}

sub len {
    my ($self) = @_;
    return $self->{len};
}

sub body {
    my ($self) = @_;
    return $self->{body};
}

########################################################################
package StandardMidiFile::ControllerEvent;

our @ISA = 'IO::KaitaiStruct::Struct';

sub from_file {
    my ($class, $filename) = @_;
    my $fd;

    open($fd, '<', $filename) or return undef;
    binmode($fd);
    return new($class, IO::KaitaiStruct::Stream->new($fd));
}

sub new {
    my ($class, $_io, $_parent, $_root) = @_;
    my $self = IO::KaitaiStruct::Struct->new($_io);

    bless $self, $class;
    $self->{_parent} = $_parent;
    $self->{_root} = $_root || $self;;

    $self->_read();

    return $self;
}

sub _read {
    my ($self) = @_;

    $self->{controller} = $self->{_io}->read_u1();
    $self->{value} = $self->{_io}->read_u1();
}

sub controller {
    my ($self) = @_;
    return $self->{controller};
}

sub value {
    my ($self) = @_;
    return $self->{value};
}

########################################################################
package StandardMidiFile::Header;

our @ISA = 'IO::KaitaiStruct::Struct';

sub from_file {
    my ($class, $filename) = @_;
    my $fd;

    open($fd, '<', $filename) or return undef;
    binmode($fd);
    return new($class, IO::KaitaiStruct::Stream->new($fd));
}

sub new {
    my ($class, $_io, $_parent, $_root) = @_;
    my $self = IO::KaitaiStruct::Struct->new($_io);

    bless $self, $class;
    $self->{_parent} = $_parent;
    $self->{_root} = $_root || $self;;

    $self->_read();

    return $self;
}

sub _read {
    my ($self) = @_;

    $self->{magic} = $self->{_io}->read_bytes(4);
    $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();
}

sub magic {
    my ($self) = @_;
    return $self->{magic};
}

sub len_header {
    my ($self) = @_;
    return $self->{len_header};
}

sub format {
    my ($self) = @_;
    return $self->{format};
}

sub num_tracks {
    my ($self) = @_;
    return $self->{num_tracks};
}

sub division {
    my ($self) = @_;
    return $self->{division};
}

########################################################################
package StandardMidiFile::SysexEventBody;

our @ISA = 'IO::KaitaiStruct::Struct';

sub from_file {
    my ($class, $filename) = @_;
    my $fd;

    open($fd, '<', $filename) or return undef;
    binmode($fd);
    return new($class, IO::KaitaiStruct::Stream->new($fd));
}

sub new {
    my ($class, $_io, $_parent, $_root) = @_;
    my $self = IO::KaitaiStruct::Struct->new($_io);

    bless $self, $class;
    $self->{_parent} = $_parent;
    $self->{_root} = $_root || $self;;

    $self->_read();

    return $self;
}

sub _read {
    my ($self) = @_;

    $self->{len} = VlqBase128Be->new($self->{_io});
    $self->{data} = $self->{_io}->read_bytes($self->len()->value());
}

sub len {
    my ($self) = @_;
    return $self->{len};
}

sub data {
    my ($self) = @_;
    return $self->{data};
}

########################################################################
package StandardMidiFile::NoteOffEvent;

our @ISA = 'IO::KaitaiStruct::Struct';

sub from_file {
    my ($class, $filename) = @_;
    my $fd;

    open($fd, '<', $filename) or return undef;
    binmode($fd);
    return new($class, IO::KaitaiStruct::Stream->new($fd));
}

sub new {
    my ($class, $_io, $_parent, $_root) = @_;
    my $self = IO::KaitaiStruct::Struct->new($_io);

    bless $self, $class;
    $self->{_parent} = $_parent;
    $self->{_root} = $_root || $self;;

    $self->_read();

    return $self;
}

sub _read {
    my ($self) = @_;

    $self->{note} = $self->{_io}->read_u1();
    $self->{velocity} = $self->{_io}->read_u1();
}

sub note {
    my ($self) = @_;
    return $self->{note};
}

sub velocity {
    my ($self) = @_;
    return $self->{velocity};
}

########################################################################
package StandardMidiFile::ChannelPressureEvent;

our @ISA = 'IO::KaitaiStruct::Struct';

sub from_file {
    my ($class, $filename) = @_;
    my $fd;

    open($fd, '<', $filename) or return undef;
    binmode($fd);
    return new($class, IO::KaitaiStruct::Stream->new($fd));
}

sub new {
    my ($class, $_io, $_parent, $_root) = @_;
    my $self = IO::KaitaiStruct::Struct->new($_io);

    bless $self, $class;
    $self->{_parent} = $_parent;
    $self->{_root} = $_root || $self;;

    $self->_read();

    return $self;
}

sub _read {
    my ($self) = @_;

    $self->{pressure} = $self->{_io}->read_u1();
}

sub pressure {
    my ($self) = @_;
    return $self->{pressure};
}

1;