123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553
<?php
if (!defined('GETID3_INCLUDEPATH')) {
exit;
}
define('GETID3_MIDI_MAGIC_MTHD', 'MThd');
define('GETID3_MIDI_MAGIC_MTRK', 'MTrk');
class getid3_midi extends getid3_handler
{
public $scanwholefile = true;
public function Analyze() {
$info = &$this->getid3->info;
$info['midi']['raw'] = array();
$thisfile_midi = &$info['midi'];
$thisfile_midi_raw = &$thisfile_midi['raw'];
$info['fileformat'] = 'midi';
$info['audio']['dataformat'] = 'midi';
$this->fseek($info['avdataoffset']);
$MIDIdata = $this->fread($this->getid3->fread_buffer_size());
$offset = 0;
$MIDIheaderID = substr($MIDIdata, $offset, 4);
if ($MIDIheaderID != GETID3_MIDI_MAGIC_MTHD) {
$this->error('Expecting "'.getid3_lib::PrintHexBytes(GETID3_MIDI_MAGIC_MTHD).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($MIDIheaderID).'"');
unset($info['fileformat']);
return false;
}
$offset += 4;
$thisfile_midi_raw['headersize'] = getid3_lib::BigEndian2Int(substr($MIDIdata, $offset, 4));
$offset += 4;
$thisfile_midi_raw['fileformat'] = getid3_lib::BigEndian2Int(substr($MIDIdata, $offset, 2));
$offset += 2;
$thisfile_midi_raw['tracks'] = getid3_lib::BigEndian2Int(substr($MIDIdata, $offset, 2));
$offset += 2;
$thisfile_midi_raw['ticksperqnote'] = getid3_lib::BigEndian2Int(substr($MIDIdata, $offset, 2));
$offset += 2;
$trackdataarray = array();
for ($i = 0; $i < $thisfile_midi_raw['tracks']; $i++) {
while ((strlen($MIDIdata) - $offset) < 8) {
if ($buffer = $this->fread($this->getid3->fread_buffer_size())) {
$MIDIdata .= $buffer;
} else {
$this->warning('only processed '.($i - 1).' of '.$thisfile_midi_raw['tracks'].' tracks');
$this->error('Unabled to read more file data at '.$this->ftell().' (trying to seek to : '.$offset.'), was expecting at least 8 more bytes');
return false;
}
}
$trackID = substr($MIDIdata, $offset, 4);
$offset += 4;
if ($trackID == GETID3_MIDI_MAGIC_MTRK) {
$tracksize = getid3_lib::BigEndian2Int(substr($MIDIdata, $offset, 4));
$offset += 4;
$trackdataarray[$i] = substr($MIDIdata, $offset, $tracksize);
$offset += $tracksize;
} else {
$this->error('Expecting "'.getid3_lib::PrintHexBytes(GETID3_MIDI_MAGIC_MTRK).'" at '.($offset - 4).', found "'.getid3_lib::PrintHexBytes($trackID).'" instead');
return false;
}
}
if (!is_array($trackdataarray) || count($trackdataarray) === 0) {
$this->error('Cannot find MIDI track information');
unset($thisfile_midi);
unset($info['fileformat']);
return false;
}
if ($this->scanwholefile) {
$thisfile_midi['totalticks'] = 0;
$info['playtime_seconds'] = 0;
$CurrentMicroSecondsPerBeat = 500000;
$MicroSecondsPerQuarterNoteAfter = array ();
$MIDIevents = array();
foreach ($trackdataarray as $tracknumber => $trackdata) {
$eventsoffset = 0;
$LastIssuedMIDIcommand = 0;
$LastIssuedMIDIchannel = 0;
$CumulativeDeltaTime = 0;
$TicksAtCurrentBPM = 0;
while ($eventsoffset < strlen($trackdata)) {
$eventid = 0;
if (isset($MIDIevents[$tracknumber])) {
$eventid = count($MIDIevents[$tracknumber]);
}
$deltatime = 0;
for ($i = 0; $i < 4; $i++) {
$deltatimebyte = ord(substr($trackdata, $eventsoffset++, 1));
$deltatime = ($deltatime << 7) + ($deltatimebyte & 0x7F);
if ($deltatimebyte & 0x80) {
} else {
break;
}
}
$CumulativeDeltaTime += $deltatime;
$TicksAtCurrentBPM += $deltatime;
$MIDIevents[$tracknumber][$eventid]['deltatime'] = $deltatime;
$MIDI_event_channel = ord(substr($trackdata, $eventsoffset++, 1));
if ($MIDI_event_channel & 0x80) {
$LastIssuedMIDIcommand = $MIDI_event_channel >> 4;
$LastIssuedMIDIchannel = $MIDI_event_channel & 0x0F;
} else {
$eventsoffset--;
}
$MIDIevents[$tracknumber][$eventid]['eventid'] = $LastIssuedMIDIcommand;
$MIDIevents[$tracknumber][$eventid]['channel'] = $LastIssuedMIDIchannel;
if ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x08) {
$notenumber = ord(substr($trackdata, $eventsoffset++, 1));
$velocity = ord(substr($trackdata, $eventsoffset++, 1));
} elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x09) {
$notenumber = ord(substr($trackdata, $eventsoffset++, 1));
$velocity = ord(substr($trackdata, $eventsoffset++, 1));
} elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0A) {
$notenumber = ord(substr($trackdata, $eventsoffset++, 1));
$velocity = ord(substr($trackdata, $eventsoffset++, 1));
} elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0B) {
$controllernum = ord(substr($trackdata, $eventsoffset++, 1));
$newvalue = ord(substr($trackdata, $eventsoffset++, 1));
} elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0C) {
$newprogramnum = ord(substr($trackdata, $eventsoffset++, 1));
$thisfile_midi_raw['track'][$tracknumber]['instrumentid'] = $newprogramnum;
if ($tracknumber == 10) {
$thisfile_midi_raw['track'][$tracknumber]['instrument'] = $this->GeneralMIDIpercussionLookup($newprogramnum);
} else {
$thisfile_midi_raw['track'][$tracknumber]['instrument'] = $this->GeneralMIDIinstrumentLookup($newprogramnum);
}
} elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0D) {
$channelnumber = ord(substr($trackdata, $eventsoffset++, 1));
} elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0E) {
$changeLSB = ord(substr($trackdata, $eventsoffset++, 1));
$changeMSB = ord(substr($trackdata, $eventsoffset++, 1));
$pitchwheelchange = (($changeMSB & 0x7F) << 7) & ($changeLSB & 0x7F);
} elseif (($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0F) && ($MIDIevents[$tracknumber][$eventid]['channel'] == 0x0F)) {
$METAeventCommand = ord(substr($trackdata, $eventsoffset++, 1));
$METAeventLength = ord(substr($trackdata, $eventsoffset++, 1));
$METAeventData = substr($trackdata, $eventsoffset, $METAeventLength);
$eventsoffset += $METAeventLength;
switch ($METAeventCommand) {
case 0x00:
$track_sequence_number = getid3_lib::BigEndian2Int(substr($METAeventData, 0, $METAeventLength));
break;
case 0x01:
$text_generic = substr($METAeventData, 0, $METAeventLength);
$thisfile_midi['comments']['comment'][] = $text_generic;
break;
case 0x02:
$text_copyright = substr($METAeventData, 0, $METAeventLength);
$thisfile_midi['comments']['copyright'][] = $text_copyright;
break;
case 0x03:
$text_trackname = substr($METAeventData, 0, $METAeventLength);
$thisfile_midi_raw['track'][$tracknumber]['name'] = $text_trackname;
break;
case 0x04:
$text_instrument = substr($METAeventData, 0, $METAeventLength);
break;
case 0x05:
$text_lyrics = substr($METAeventData, 0, $METAeventLength);
if (!isset($thisfile_midi['lyrics'])) {
$thisfile_midi['lyrics'] = '';
}
$thisfile_midi['lyrics'] .= $text_lyrics."\n";
break;
case 0x06:
$text_marker = substr($METAeventData, 0, $METAeventLength);
break;
case 0x07:
$text_cuepoint = substr($METAeventData, 0, $METAeventLength);
break;
case 0x2F:
break;
case 0x51:
$CurrentMicroSecondsPerBeat = getid3_lib::BigEndian2Int(substr($METAeventData, 0, $METAeventLength));
if ($CurrentMicroSecondsPerBeat == 0) {
$this->error('Corrupt MIDI file: CurrentMicroSecondsPerBeat == zero');
return false;
}
$thisfile_midi_raw['events'][$tracknumber][$CumulativeDeltaTime]['us_qnote'] = $CurrentMicroSecondsPerBeat;
$MicroSecondsPerQuarterNoteAfter[$CumulativeDeltaTime] = $CurrentMicroSecondsPerBeat;
$TicksAtCurrentBPM = 0;
break;
case 0x58:
$timesig_numerator = getid3_lib::BigEndian2Int($METAeventData[0]);
$timesig_denominator = pow(2, getid3_lib::BigEndian2Int($METAeventData[1]));
$timesig_32inqnote = getid3_lib::BigEndian2Int($METAeventData[2]);
$thisfile_midi['timesignature'][] = $timesig_numerator.'/'.$timesig_denominator;
break;
case 0x59:
$keysig_sharpsflats = getid3_lib::BigEndian2Int($METAeventData[0]);
if ($keysig_sharpsflats & 0x80) {
$keysig_sharpsflats -= 256;
}
$keysig_majorminor = getid3_lib::BigEndian2Int($METAeventData[1]);
$keysigs = array(-7=>'Cb', -6=>'Gb', -5=>'Db', -4=>'Ab', -3=>'Eb', -2=>'Bb', -1=>'F', 0=>'C', 1=>'G', 2=>'D', 3=>'A', 4=>'E', 5=>'B', 6=>'F#', 7=>'C#');
$thisfile_midi['keysignature'][] = $keysigs[$keysig_sharpsflats].' '.((bool) $keysig_majorminor ? 'minor' : 'major');
break;
case 0x7F:
$custom_data = substr($METAeventData, 0, $METAeventLength);
break;
default:
$this->warning('Unhandled META Event Command: '.$METAeventCommand);
break;
}
} else {
$this->warning('Unhandled MIDI Event ID: '.$MIDIevents[$tracknumber][$eventid]['eventid'].' + Channel ID: '.$MIDIevents[$tracknumber][$eventid]['channel']);
}
}
if (($tracknumber > 0) || (count($trackdataarray) == 1)) {
$thisfile_midi['totalticks'] = max($thisfile_midi['totalticks'], $CumulativeDeltaTime);
}
}
$previoustickoffset = null;
$prevmicrosecondsperbeat = null;
ksort($MicroSecondsPerQuarterNoteAfter);
foreach ($MicroSecondsPerQuarterNoteAfter as $tickoffset => $microsecondsperbeat) {
if (is_null($previoustickoffset)) {
$prevmicrosecondsperbeat = $microsecondsperbeat;
$previoustickoffset = $tickoffset;
continue;
}
if ($thisfile_midi['totalticks'] > $tickoffset) {
if ($thisfile_midi_raw['ticksperqnote'] == 0) {
$this->error('Corrupt MIDI file: ticksperqnote == zero');
return false;
}
$info['playtime_seconds'] += (($tickoffset - $previoustickoffset) / $thisfile_midi_raw['ticksperqnote']) * ($prevmicrosecondsperbeat / 1000000);
$prevmicrosecondsperbeat = $microsecondsperbeat;
$previoustickoffset = $tickoffset;
}
}
if ($thisfile_midi['totalticks'] > $previoustickoffset) {
if ($thisfile_midi_raw['ticksperqnote'] == 0) {
$this->error('Corrupt MIDI file: ticksperqnote == zero');
return false;
}
$info['playtime_seconds'] += (($thisfile_midi['totalticks'] - $previoustickoffset) / $thisfile_midi_raw['ticksperqnote']) * ($prevmicrosecondsperbeat / 1000000);
}
}
if (!empty($info['playtime_seconds'])) {
$info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
}
if (!empty($thisfile_midi['lyrics'])) {
$thisfile_midi['comments']['lyrics'][] = $thisfile_midi['lyrics'];
}
return true;
}
public function GeneralMIDIinstrumentLookup($instrumentid) {
$begin = __LINE__;
return getid3_lib::EmbeddedLookup($instrumentid, $begin, __LINE__, __FILE__, 'GeneralMIDIinstrument');
}
public function GeneralMIDIpercussionLookup($instrumentid) {
$begin = __LINE__;
return getid3_lib::EmbeddedLookup($instrumentid, $begin, __LINE__, __FILE__, 'GeneralMIDIpercussion');
}
}