20 function getid3_midi(&$fd, &$ThisFileInfo, $scanwholefile=
true) {
23 $ThisFileInfo[
'midi'][
'raw'] = array();
24 $thisfile_midi = &$ThisFileInfo[
'midi'];
25 $thisfile_midi_raw = &$thisfile_midi[
'raw'];
27 $ThisFileInfo[
'fileformat'] =
'midi';
28 $ThisFileInfo[
'audio'][
'dataformat'] =
'midi';
30 fseek($fd, $ThisFileInfo[
'avdataoffset'], SEEK_SET);
33 $MIDIheaderID = substr($MIDIdata, $offset, 4);
34 if ($MIDIheaderID !=
'MThd') {
35 $ThisFileInfo[
'error'][] =
'Expecting "MThd" at offset '.$ThisFileInfo[
'avdataoffset'].
', found "'.$MIDIheaderID.
'"';
36 unset($ThisFileInfo[
'fileformat']);
49 for ($i = 0; $i < $thisfile_midi_raw[
'tracks']; $i++) {
50 if ((strlen($MIDIdata) - $offset) < 8) {
53 $trackID = substr($MIDIdata, $offset, 4);
55 if ($trackID ==
'MTrk') {
59 $trackdataarray[$i] = substr($MIDIdata, $offset, $tracksize);
60 $offset += $tracksize;
62 $ThisFileInfo[
'error'][] =
'Expecting "MTrk" at '.$offset.
', found '.$trackID.
' instead';
67 if (!isset($trackdataarray) || !is_array($trackdataarray)) {
68 $ThisFileInfo[
'error'][] =
'Cannot find MIDI track information';
69 unset($thisfile_midi);
70 unset($ThisFileInfo[
'fileformat']);
75 $thisfile_midi[
'totalticks'] = 0;
76 $ThisFileInfo[
'playtime_seconds'] = 0;
77 $CurrentMicroSecondsPerBeat = 500000;
78 $CurrentBeatsPerMinute = 120;
80 foreach ($trackdataarray as $tracknumber => $trackdata) {
83 $LastIssuedMIDIcommand = 0;
84 $LastIssuedMIDIchannel = 0;
85 $CumulativeDeltaTime = 0;
86 $TicksAtCurrentBPM = 0;
87 while ($eventsoffset < strlen($trackdata)) {
89 if (isset($MIDIevents[$tracknumber]) && is_array($MIDIevents[$tracknumber])) {
90 $eventid = count($MIDIevents[$tracknumber]);
93 for ($i = 0; $i < 4; $i++) {
94 $deltatimebyte = ord(substr($trackdata, $eventsoffset++, 1));
95 $deltatime = ($deltatime << 7) + ($deltatimebyte & 0x7F);
96 if ($deltatimebyte & 0x80) {
102 $CumulativeDeltaTime += $deltatime;
103 $TicksAtCurrentBPM += $deltatime;
104 $MIDIevents[$tracknumber][$eventid][
'deltatime'] = $deltatime;
105 $MIDI_event_channel = ord(substr($trackdata, $eventsoffset++, 1));
106 if ($MIDI_event_channel & 0x80) {
108 $LastIssuedMIDIcommand = $MIDI_event_channel >> 4;
109 $LastIssuedMIDIchannel = $MIDI_event_channel & 0x0F;
114 $MIDIevents[$tracknumber][$eventid][
'eventid'] = $LastIssuedMIDIcommand;
115 $MIDIevents[$tracknumber][$eventid][
'channel'] = $LastIssuedMIDIchannel;
116 if ($MIDIevents[$tracknumber][$eventid][
'eventid'] == 0x08) {
118 $notenumber = ord(substr($trackdata, $eventsoffset++, 1));
119 $velocity = ord(substr($trackdata, $eventsoffset++, 1));
121 } elseif ($MIDIevents[$tracknumber][$eventid][
'eventid'] == 0x09) {
123 $notenumber = ord(substr($trackdata, $eventsoffset++, 1));
124 $velocity = ord(substr($trackdata, $eventsoffset++, 1));
126 } elseif ($MIDIevents[$tracknumber][$eventid][
'eventid'] == 0x0A) {
128 $notenumber = ord(substr($trackdata, $eventsoffset++, 1));
129 $velocity = ord(substr($trackdata, $eventsoffset++, 1));
131 } elseif ($MIDIevents[$tracknumber][$eventid][
'eventid'] == 0x0B) {
133 $controllernum = ord(substr($trackdata, $eventsoffset++, 1));
134 $newvalue = ord(substr($trackdata, $eventsoffset++, 1));
136 } elseif ($MIDIevents[$tracknumber][$eventid][
'eventid'] == 0x0C) {
138 $newprogramnum = ord(substr($trackdata, $eventsoffset++, 1));
140 $thisfile_midi_raw[
'track'][$tracknumber][
'instrumentid'] = $newprogramnum;
141 if ($tracknumber == 10) {
147 } elseif ($MIDIevents[$tracknumber][$eventid][
'eventid'] == 0x0D) {
149 $channelnumber = ord(substr($trackdata, $eventsoffset++, 1));
151 } elseif ($MIDIevents[$tracknumber][$eventid][
'eventid'] == 0x0E) {
153 $changeLSB = ord(substr($trackdata, $eventsoffset++, 1));
154 $changeMSB = ord(substr($trackdata, $eventsoffset++, 1));
155 $pitchwheelchange = (($changeMSB & 0x7F) << 7) & ($changeLSB & 0x7F);
157 } elseif (($MIDIevents[$tracknumber][$eventid][
'eventid'] == 0x0F) && ($MIDIevents[$tracknumber][$eventid][
'channel'] == 0x0F)) {
159 $METAeventCommand = ord(substr($trackdata, $eventsoffset++, 1));
160 $METAeventLength = ord(substr($trackdata, $eventsoffset++, 1));
161 $METAeventData = substr($trackdata, $eventsoffset, $METAeventLength);
162 $eventsoffset += $METAeventLength;
163 switch ($METAeventCommand) {
170 $text_generic = substr($METAeventData, 0, $METAeventLength);
172 $thisfile_midi[
'comments'][
'comment'][] = $text_generic;
176 $text_copyright = substr($METAeventData, 0, $METAeventLength);
178 $thisfile_midi[
'comments'][
'copyright'][] = $text_copyright;
182 $text_trackname = substr($METAeventData, 0, $METAeventLength);
183 $thisfile_midi_raw[
'track'][$tracknumber][
'name'] = $text_trackname;
187 $text_instrument = substr($METAeventData, 0, $METAeventLength);
192 $text_lyrics = substr($METAeventData, 0, $METAeventLength);
194 if (!isset($thisfile_midi[
'lyrics'])) {
195 $thisfile_midi[
'lyrics'] =
'';
197 $thisfile_midi[
'lyrics'] .= $text_lyrics.
"\n";
201 $text_marker = substr($METAeventData, 0, $METAeventLength);
206 $text_cuepoint = substr($METAeventData, 0, $METAeventLength);
216 if ($CurrentMicroSecondsPerBeat == 0) {
217 $ThisFileInfo[
'error'][] =
'Corrupt MIDI file: CurrentMicroSecondsPerBeat == zero';
220 $thisfile_midi_raw[
'events'][$tracknumber][$CumulativeDeltaTime][
'us_qnote'] = $CurrentMicroSecondsPerBeat;
221 $CurrentBeatsPerMinute = (1000000 / $CurrentMicroSecondsPerBeat) * 60;
222 $MicroSecondsPerQuarterNoteAfter[$CumulativeDeltaTime] = $CurrentMicroSecondsPerBeat;
223 $TicksAtCurrentBPM = 0;
234 $thisfile_midi[
'timesignature'][] = $timesig_numerator.
'/'.$timesig_denominator;
239 if ($keysig_sharpsflats & 0x80) {
241 $keysig_sharpsflats -= 256;
245 $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#');
252 $thisfile_midi[
'keysignature'][] = $keysigs[$keysig_sharpsflats].
' '.((bool) $keysig_majorminor ?
'minor' :
'major');
256 $custom_data = substr($METAeventData, 0, $METAeventLength);
260 $ThisFileInfo[
'warning'][] =
'Unhandled META Event Command: '.$METAeventCommand;
266 $ThisFileInfo[
'warning'][] =
'Unhandled MIDI Event ID: '.$MIDIevents[$tracknumber][$eventid][
'eventid'].
' + Channel ID: '.$MIDIevents[$tracknumber][$eventid][
'channel'];
270 if (($tracknumber > 0) || (count($trackdataarray) == 1)) {
271 $thisfile_midi[
'totalticks'] = max($thisfile_midi[
'totalticks'], $CumulativeDeltaTime);
274 $previoustickoffset = null;
276 ksort($MicroSecondsPerQuarterNoteAfter);
277 foreach ($MicroSecondsPerQuarterNoteAfter as $tickoffset => $microsecondsperbeat) {
278 if (is_null($previoustickoffset)) {
279 $prevmicrosecondsperbeat = $microsecondsperbeat;
280 $previoustickoffset = $tickoffset;
283 if ($thisfile_midi[
'totalticks'] > $tickoffset) {
285 if ($thisfile_midi_raw[
'ticksperqnote'] == 0) {
286 $ThisFileInfo[
'error'][] =
'Corrupt MIDI file: ticksperqnote == zero';
290 $ThisFileInfo[
'playtime_seconds'] += (($tickoffset - $previoustickoffset) / $thisfile_midi_raw[
'ticksperqnote']) * ($prevmicrosecondsperbeat / 1000000);
292 $prevmicrosecondsperbeat = $microsecondsperbeat;
293 $previoustickoffset = $tickoffset;
296 if ($thisfile_midi[
'totalticks'] > $previoustickoffset) {
298 if ($thisfile_midi_raw[
'ticksperqnote'] == 0) {
299 $ThisFileInfo[
'error'][] =
'Corrupt MIDI file: ticksperqnote == zero';
303 $ThisFileInfo[
'playtime_seconds'] += (($thisfile_midi[
'totalticks'] - $previoustickoffset) / $thisfile_midi_raw[
'ticksperqnote']) * ($microsecondsperbeat / 1000000);
308 if ($ThisFileInfo[
'playtime_seconds'] > 0) {
309 $ThisFileInfo[
'bitrate'] = (($ThisFileInfo[
'avdataend'] - $ThisFileInfo[
'avdataoffset']) * 8) / $ThisFileInfo[
'playtime_seconds'];
312 if (!empty($thisfile_midi[
'lyrics'])) {
313 $thisfile_midi[
'comments'][
'lyrics'][] = $thisfile_midi[
'lyrics'];