359aca26679ccd56ad42a7891abac75ea235edc9
2 /////////////////////////////////////////////////////////////////
3 /// getID3() by James Heinrich <info@getid3.org> //
4 // available at http://getid3.sourceforge.net //
5 // or http://www.getid3.org //
6 // also https://github.com/JamesHeinrich/getID3 //
7 /////////////////////////////////////////////////////////////////
8 // See readme.txt for more details //
9 /////////////////////////////////////////////////////////////////
11 // module.audio.midi.php //
12 // module for Midi Audio files //
13 // dependencies: NONE //
15 /////////////////////////////////////////////////////////////////
17 define('GETID3_MIDI_MAGIC_MTHD', 'MThd'); // MIDI file header magic
18 define('GETID3_MIDI_MAGIC_MTRK', 'MTrk'); // MIDI track header magic
20 class getid3_midi
extends getid3_handler
22 public $scanwholefile = true;
24 public function Analyze() {
25 $info = &$this->getid3
->info
;
28 $info['midi']['raw'] = array();
29 $thisfile_midi = &$info['midi'];
30 $thisfile_midi_raw = &$thisfile_midi['raw'];
32 $info['fileformat'] = 'midi';
33 $info['audio']['dataformat'] = 'midi';
35 $this->fseek($info['avdataoffset']);
36 $MIDIdata = $this->fread($this->getid3
->fread_buffer_size());
38 $MIDIheaderID = substr($MIDIdata, $offset, 4); // 'MThd'
39 if ($MIDIheaderID != GETID3_MIDI_MAGIC_MTHD
) {
40 $this->error('Expecting "'.getid3_lib
::PrintHexBytes(GETID3_MIDI_MAGIC_MTHD
).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib
::PrintHexBytes($MIDIheaderID).'"');
41 unset($info['fileformat']);
45 $thisfile_midi_raw['headersize'] = getid3_lib
::BigEndian2Int(substr($MIDIdata, $offset, 4));
47 $thisfile_midi_raw['fileformat'] = getid3_lib
::BigEndian2Int(substr($MIDIdata, $offset, 2));
49 $thisfile_midi_raw['tracks'] = getid3_lib
::BigEndian2Int(substr($MIDIdata, $offset, 2));
51 $thisfile_midi_raw['ticksperqnote'] = getid3_lib
::BigEndian2Int(substr($MIDIdata, $offset, 2));
54 for ($i = 0; $i < $thisfile_midi_raw['tracks']; $i++
) {
55 while ((strlen($MIDIdata) - $offset) < 8) {
56 if ($buffer = $this->fread($this->getid3
->fread_buffer_size())) {
59 $this->warning('only processed '.($i - 1).' of '.$thisfile_midi_raw['tracks'].' tracks');
60 $this->error('Unabled to read more file data at '.$this->ftell().' (trying to seek to : '.$offset.'), was expecting at least 8 more bytes');
64 $trackID = substr($MIDIdata, $offset, 4);
66 if ($trackID == GETID3_MIDI_MAGIC_MTRK
) {
67 $tracksize = getid3_lib
::BigEndian2Int(substr($MIDIdata, $offset, 4));
69 //$thisfile_midi['tracks'][$i]['size'] = $tracksize;
70 $trackdataarray[$i] = substr($MIDIdata, $offset, $tracksize);
71 $offset +
= $tracksize;
73 $this->error('Expecting "'.getid3_lib
::PrintHexBytes(GETID3_MIDI_MAGIC_MTRK
).'" at '.($offset - 4).', found "'.getid3_lib
::PrintHexBytes($trackID).'" instead');
78 if (!isset($trackdataarray) ||
!is_array($trackdataarray)) {
79 $this->error('Cannot find MIDI track information');
80 unset($thisfile_midi);
81 unset($info['fileformat']);
85 if ($this->scanwholefile
) { // this can take quite a long time, so have the option to bypass it if speed is very important
86 $thisfile_midi['totalticks'] = 0;
87 $info['playtime_seconds'] = 0;
88 $CurrentMicroSecondsPerBeat = 500000; // 120 beats per minute; 60,000,000 microseconds per minute -> 500,000 microseconds per beat
89 $CurrentBeatsPerMinute = 120; // 120 beats per minute; 60,000,000 microseconds per minute -> 500,000 microseconds per beat
90 $MicroSecondsPerQuarterNoteAfter = array ();
92 foreach ($trackdataarray as $tracknumber => $trackdata) {
95 $LastIssuedMIDIcommand = 0;
96 $LastIssuedMIDIchannel = 0;
97 $CumulativeDeltaTime = 0;
98 $TicksAtCurrentBPM = 0;
99 while ($eventsoffset < strlen($trackdata)) {
101 if (isset($MIDIevents[$tracknumber]) && is_array($MIDIevents[$tracknumber])) {
102 $eventid = count($MIDIevents[$tracknumber]);
105 for ($i = 0; $i < 4; $i++
) {
106 $deltatimebyte = ord(substr($trackdata, $eventsoffset++
, 1));
107 $deltatime = ($deltatime << 7) +
($deltatimebyte & 0x7F);
108 if ($deltatimebyte & 0x80) {
109 // another byte follows
114 $CumulativeDeltaTime +
= $deltatime;
115 $TicksAtCurrentBPM +
= $deltatime;
116 $MIDIevents[$tracknumber][$eventid]['deltatime'] = $deltatime;
117 $MIDI_event_channel = ord(substr($trackdata, $eventsoffset++
, 1));
118 if ($MIDI_event_channel & 0x80) {
119 // OK, normal event - MIDI command has MSB set
120 $LastIssuedMIDIcommand = $MIDI_event_channel >> 4;
121 $LastIssuedMIDIchannel = $MIDI_event_channel & 0x0F;
123 // running event - assume last command
126 $MIDIevents[$tracknumber][$eventid]['eventid'] = $LastIssuedMIDIcommand;
127 $MIDIevents[$tracknumber][$eventid]['channel'] = $LastIssuedMIDIchannel;
128 if ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x08) { // Note off (key is released)
130 $notenumber = ord(substr($trackdata, $eventsoffset++
, 1));
131 $velocity = ord(substr($trackdata, $eventsoffset++
, 1));
133 } elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x09) { // Note on (key is pressed)
135 $notenumber = ord(substr($trackdata, $eventsoffset++
, 1));
136 $velocity = ord(substr($trackdata, $eventsoffset++
, 1));
138 } elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0A) { // Key after-touch
140 $notenumber = ord(substr($trackdata, $eventsoffset++
, 1));
141 $velocity = ord(substr($trackdata, $eventsoffset++
, 1));
143 } elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0B) { // Control Change
145 $controllernum = ord(substr($trackdata, $eventsoffset++
, 1));
146 $newvalue = ord(substr($trackdata, $eventsoffset++
, 1));
148 } elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0C) { // Program (patch) change
150 $newprogramnum = ord(substr($trackdata, $eventsoffset++
, 1));
152 $thisfile_midi_raw['track'][$tracknumber]['instrumentid'] = $newprogramnum;
153 if ($tracknumber == 10) {
154 $thisfile_midi_raw['track'][$tracknumber]['instrument'] = $this->GeneralMIDIpercussionLookup($newprogramnum);
156 $thisfile_midi_raw['track'][$tracknumber]['instrument'] = $this->GeneralMIDIinstrumentLookup($newprogramnum);
159 } elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0D) { // Channel after-touch
161 $channelnumber = ord(substr($trackdata, $eventsoffset++
, 1));
163 } elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0E) { // Pitch wheel change (2000H is normal or no change)
165 $changeLSB = ord(substr($trackdata, $eventsoffset++
, 1));
166 $changeMSB = ord(substr($trackdata, $eventsoffset++
, 1));
167 $pitchwheelchange = (($changeMSB & 0x7F) << 7) & ($changeLSB & 0x7F);
169 } elseif (($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0F) && ($MIDIevents[$tracknumber][$eventid]['channel'] == 0x0F)) {
171 $METAeventCommand = ord(substr($trackdata, $eventsoffset++
, 1));
172 $METAeventLength = ord(substr($trackdata, $eventsoffset++
, 1));
173 $METAeventData = substr($trackdata, $eventsoffset, $METAeventLength);
174 $eventsoffset +
= $METAeventLength;
175 switch ($METAeventCommand) {
176 case 0x00: // Set track sequence number
177 $track_sequence_number = getid3_lib
::BigEndian2Int(substr($METAeventData, 0, $METAeventLength));
178 //$thisfile_midi_raw['events'][$tracknumber][$eventid]['seqno'] = $track_sequence_number;
181 case 0x01: // Text: generic
182 $text_generic = substr($METAeventData, 0, $METAeventLength);
183 //$thisfile_midi_raw['events'][$tracknumber][$eventid]['text'] = $text_generic;
184 $thisfile_midi['comments']['comment'][] = $text_generic;
187 case 0x02: // Text: copyright
188 $text_copyright = substr($METAeventData, 0, $METAeventLength);
189 //$thisfile_midi_raw['events'][$tracknumber][$eventid]['copyright'] = $text_copyright;
190 $thisfile_midi['comments']['copyright'][] = $text_copyright;
193 case 0x03: // Text: track name
194 $text_trackname = substr($METAeventData, 0, $METAeventLength);
195 $thisfile_midi_raw['track'][$tracknumber]['name'] = $text_trackname;
198 case 0x04: // Text: track instrument name
199 $text_instrument = substr($METAeventData, 0, $METAeventLength);
200 //$thisfile_midi_raw['events'][$tracknumber][$eventid]['instrument'] = $text_instrument;
203 case 0x05: // Text: lyrics
204 $text_lyrics = substr($METAeventData, 0, $METAeventLength);
205 //$thisfile_midi_raw['events'][$tracknumber][$eventid]['lyrics'] = $text_lyrics;
206 if (!isset($thisfile_midi['lyrics'])) {
207 $thisfile_midi['lyrics'] = '';
209 $thisfile_midi['lyrics'] .= $text_lyrics."\n";
212 case 0x06: // Text: marker
213 $text_marker = substr($METAeventData, 0, $METAeventLength);
214 //$thisfile_midi_raw['events'][$tracknumber][$eventid]['marker'] = $text_marker;
217 case 0x07: // Text: cue point
218 $text_cuepoint = substr($METAeventData, 0, $METAeventLength);
219 //$thisfile_midi_raw['events'][$tracknumber][$eventid]['cuepoint'] = $text_cuepoint;
222 case 0x2F: // End Of Track
223 //$thisfile_midi_raw['events'][$tracknumber][$eventid]['EOT'] = $CumulativeDeltaTime;
226 case 0x51: // Tempo: microseconds / quarter note
227 $CurrentMicroSecondsPerBeat = getid3_lib
::BigEndian2Int(substr($METAeventData, 0, $METAeventLength));
228 if ($CurrentMicroSecondsPerBeat == 0) {
229 $this->error('Corrupt MIDI file: CurrentMicroSecondsPerBeat == zero');
232 $thisfile_midi_raw['events'][$tracknumber][$CumulativeDeltaTime]['us_qnote'] = $CurrentMicroSecondsPerBeat;
233 $CurrentBeatsPerMinute = (1000000 / $CurrentMicroSecondsPerBeat) * 60;
234 $MicroSecondsPerQuarterNoteAfter[$CumulativeDeltaTime] = $CurrentMicroSecondsPerBeat;
235 $TicksAtCurrentBPM = 0;
238 case 0x58: // Time signature
239 $timesig_numerator = getid3_lib
::BigEndian2Int($METAeventData{0});
240 $timesig_denominator = pow(2, getid3_lib
::BigEndian2Int($METAeventData{1})); // $02 -> x/4, $03 -> x/8, etc
241 $timesig_32inqnote = getid3_lib
::BigEndian2Int($METAeventData{2}); // number of 32nd notes to the quarter note
242 //$thisfile_midi_raw['events'][$tracknumber][$eventid]['timesig_32inqnote'] = $timesig_32inqnote;
243 //$thisfile_midi_raw['events'][$tracknumber][$eventid]['timesig_numerator'] = $timesig_numerator;
244 //$thisfile_midi_raw['events'][$tracknumber][$eventid]['timesig_denominator'] = $timesig_denominator;
245 //$thisfile_midi_raw['events'][$tracknumber][$eventid]['timesig_text'] = $timesig_numerator.'/'.$timesig_denominator;
246 $thisfile_midi['timesignature'][] = $timesig_numerator.'/'.$timesig_denominator;
249 case 0x59: // Keysignature
250 $keysig_sharpsflats = getid3_lib
::BigEndian2Int($METAeventData{0});
251 if ($keysig_sharpsflats & 0x80) {
252 // (-7 -> 7 flats, 0 ->key of C, 7 -> 7 sharps)
253 $keysig_sharpsflats -= 256;
256 $keysig_majorminor = getid3_lib
::BigEndian2Int($METAeventData{1}); // 0 -> major, 1 -> minor
257 $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#');
258 //$thisfile_midi_raw['events'][$tracknumber][$eventid]['keysig_sharps'] = (($keysig_sharpsflats > 0) ? abs($keysig_sharpsflats) : 0);
259 //$thisfile_midi_raw['events'][$tracknumber][$eventid]['keysig_flats'] = (($keysig_sharpsflats < 0) ? abs($keysig_sharpsflats) : 0);
260 //$thisfile_midi_raw['events'][$tracknumber][$eventid]['keysig_minor'] = (bool) $keysig_majorminor;
261 //$thisfile_midi_raw['events'][$tracknumber][$eventid]['keysig_text'] = $keysigs[$keysig_sharpsflats].' '.($thisfile_midi_raw['events'][$tracknumber][$eventid]['keysig_minor'] ? 'minor' : 'major');
263 // $keysigs[$keysig_sharpsflats] gets an int key (correct) - $keysigs["$keysig_sharpsflats"] gets a string key (incorrect)
264 $thisfile_midi['keysignature'][] = $keysigs[$keysig_sharpsflats].' '.((bool) $keysig_majorminor ?
'minor' : 'major');
267 case 0x7F: // Sequencer specific information
268 $custom_data = substr($METAeventData, 0, $METAeventLength);
272 $this->warning('Unhandled META Event Command: '.$METAeventCommand);
278 $this->warning('Unhandled MIDI Event ID: '.$MIDIevents[$tracknumber][$eventid]['eventid'].' + Channel ID: '.$MIDIevents[$tracknumber][$eventid]['channel']);
282 if (($tracknumber > 0) ||
(count($trackdataarray) == 1)) {
283 $thisfile_midi['totalticks'] = max($thisfile_midi['totalticks'], $CumulativeDeltaTime);
286 $previoustickoffset = null;
288 ksort($MicroSecondsPerQuarterNoteAfter);
289 foreach ($MicroSecondsPerQuarterNoteAfter as $tickoffset => $microsecondsperbeat) {
290 if (is_null($previoustickoffset)) {
291 $prevmicrosecondsperbeat = $microsecondsperbeat;
292 $previoustickoffset = $tickoffset;
295 if ($thisfile_midi['totalticks'] > $tickoffset) {
297 if ($thisfile_midi_raw['ticksperqnote'] == 0) {
298 $this->error('Corrupt MIDI file: ticksperqnote == zero');
302 $info['playtime_seconds'] +
= (($tickoffset - $previoustickoffset) / $thisfile_midi_raw['ticksperqnote']) * ($prevmicrosecondsperbeat / 1000000);
304 $prevmicrosecondsperbeat = $microsecondsperbeat;
305 $previoustickoffset = $tickoffset;
308 if ($thisfile_midi['totalticks'] > $previoustickoffset) {
310 if ($thisfile_midi_raw['ticksperqnote'] == 0) {
311 $this->error('Corrupt MIDI file: ticksperqnote == zero');
315 $info['playtime_seconds'] +
= (($thisfile_midi['totalticks'] - $previoustickoffset) / $thisfile_midi_raw['ticksperqnote']) * ($microsecondsperbeat / 1000000);
321 if (!empty($info['playtime_seconds'])) {
322 $info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
325 if (!empty($thisfile_midi['lyrics'])) {
326 $thisfile_midi['comments']['lyrics'][] = $thisfile_midi['lyrics'];
332 public function GeneralMIDIinstrumentLookup($instrumentid) {
336 /** This is not a comment!
362 24 Acoustic Guitar (nylon)
363 25 Acoustic Guitar (steel)
364 26 Electric Guitar (jazz)
365 27 Electric Guitar (clean)
366 28 Electric Guitar (muted)
371 33 Electric Bass (finger)
372 34 Electric Bass (pick)
384 46 Orchestral Strings
425 87 Lead 8 (bass + lead)
438 100 FX 5 (brightness)
458 120 Guitar Fret Noise
469 return getid3_lib
::EmbeddedLookup($instrumentid, $begin, __LINE__
, __FILE__
, 'GeneralMIDIinstrument');
472 public function GeneralMIDIpercussionLookup($instrumentid) {
476 /** This is not a comment!
478 35 Acoustic Bass Drum
527 return getid3_lib
::EmbeddedLookup($instrumentid, $begin, __LINE__
, __FILE__
, 'GeneralMIDIpercussion');