1<?php 2 3///////////////////////////////////////////////////////////////// 4/// getID3() by James Heinrich <info@getid3.org> // 5// available at https://github.com/JamesHeinrich/getID3 // 6// or https://www.getid3.org // 7// or http://getid3.sourceforge.net // 8// see readme.txt for more details // 9///////////////////////////////////////////////////////////////// 10// // 11// module.audio.midi.php // 12// module for Midi Audio files // 13// dependencies: NONE // 14// /// 15///////////////////////////////////////////////////////////////// 16 17if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers 18 exit; 19} 20 21define('GETID3_MIDI_MAGIC_MTHD', 'MThd'); // MIDI file header magic 22define('GETID3_MIDI_MAGIC_MTRK', 'MTrk'); // MIDI track header magic 23 24class getid3_midi extends getid3_handler 25{ 26 /** 27 * @var bool 28 */ 29 public $scanwholefile = true; 30 31 /** 32 * @return bool 33 */ 34 public function Analyze() { 35 $info = &$this->getid3->info; 36 37 // shortcut 38 $info['midi']['raw'] = array(); 39 $thisfile_midi = &$info['midi']; 40 $thisfile_midi_raw = &$thisfile_midi['raw']; 41 42 $info['fileformat'] = 'midi'; 43 $info['audio']['dataformat'] = 'midi'; 44 45 $this->fseek($info['avdataoffset']); 46 $MIDIdata = $this->fread($this->getid3->fread_buffer_size()); 47 $offset = 0; 48 $MIDIheaderID = substr($MIDIdata, $offset, 4); // 'MThd' 49 if ($MIDIheaderID != GETID3_MIDI_MAGIC_MTHD) { 50 $this->error('Expecting "'.getid3_lib::PrintHexBytes(GETID3_MIDI_MAGIC_MTHD).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($MIDIheaderID).'"'); 51 unset($info['fileformat']); 52 return false; 53 } 54 $offset += 4; 55 $thisfile_midi_raw['headersize'] = getid3_lib::BigEndian2Int(substr($MIDIdata, $offset, 4)); 56 $offset += 4; 57 $thisfile_midi_raw['fileformat'] = getid3_lib::BigEndian2Int(substr($MIDIdata, $offset, 2)); 58 $offset += 2; 59 $thisfile_midi_raw['tracks'] = getid3_lib::BigEndian2Int(substr($MIDIdata, $offset, 2)); 60 $offset += 2; 61 $thisfile_midi_raw['ticksperqnote'] = getid3_lib::BigEndian2Int(substr($MIDIdata, $offset, 2)); 62 $offset += 2; 63 64 for ($i = 0; $i < $thisfile_midi_raw['tracks']; $i++) { 65 while ((strlen($MIDIdata) - $offset) < 8) { 66 if ($buffer = $this->fread($this->getid3->fread_buffer_size())) { 67 $MIDIdata .= $buffer; 68 } else { 69 $this->warning('only processed '.($i - 1).' of '.$thisfile_midi_raw['tracks'].' tracks'); 70 $this->error('Unabled to read more file data at '.$this->ftell().' (trying to seek to : '.$offset.'), was expecting at least 8 more bytes'); 71 return false; 72 } 73 } 74 $trackID = substr($MIDIdata, $offset, 4); 75 $offset += 4; 76 if ($trackID == GETID3_MIDI_MAGIC_MTRK) { 77 $tracksize = getid3_lib::BigEndian2Int(substr($MIDIdata, $offset, 4)); 78 $offset += 4; 79 //$thisfile_midi['tracks'][$i]['size'] = $tracksize; 80 $trackdataarray[$i] = substr($MIDIdata, $offset, $tracksize); 81 $offset += $tracksize; 82 } else { 83 $this->error('Expecting "'.getid3_lib::PrintHexBytes(GETID3_MIDI_MAGIC_MTRK).'" at '.($offset - 4).', found "'.getid3_lib::PrintHexBytes($trackID).'" instead'); 84 return false; 85 } 86 } 87 88 if (!isset($trackdataarray) || !is_array($trackdataarray)) { 89 $this->error('Cannot find MIDI track information'); 90 unset($thisfile_midi); 91 unset($info['fileformat']); 92 return false; 93 } 94 95 if ($this->scanwholefile) { // this can take quite a long time, so have the option to bypass it if speed is very important 96 $thisfile_midi['totalticks'] = 0; 97 $info['playtime_seconds'] = 0; 98 $CurrentMicroSecondsPerBeat = 500000; // 120 beats per minute; 60,000,000 microseconds per minute -> 500,000 microseconds per beat 99 $CurrentBeatsPerMinute = 120; // 120 beats per minute; 60,000,000 microseconds per minute -> 500,000 microseconds per beat 100 $MicroSecondsPerQuarterNoteAfter = array (); 101 $MIDIevents = array(); 102 103 foreach ($trackdataarray as $tracknumber => $trackdata) { 104 105 $eventsoffset = 0; 106 $LastIssuedMIDIcommand = 0; 107 $LastIssuedMIDIchannel = 0; 108 $CumulativeDeltaTime = 0; 109 $TicksAtCurrentBPM = 0; 110 while ($eventsoffset < strlen($trackdata)) { 111 $eventid = 0; 112 if (isset($MIDIevents[$tracknumber]) && is_array($MIDIevents[$tracknumber])) { 113 $eventid = count($MIDIevents[$tracknumber]); 114 } 115 $deltatime = 0; 116 for ($i = 0; $i < 4; $i++) { 117 $deltatimebyte = ord(substr($trackdata, $eventsoffset++, 1)); 118 $deltatime = ($deltatime << 7) + ($deltatimebyte & 0x7F); 119 if ($deltatimebyte & 0x80) { 120 // another byte follows 121 } else { 122 break; 123 } 124 } 125 $CumulativeDeltaTime += $deltatime; 126 $TicksAtCurrentBPM += $deltatime; 127 $MIDIevents[$tracknumber][$eventid]['deltatime'] = $deltatime; 128 $MIDI_event_channel = ord(substr($trackdata, $eventsoffset++, 1)); 129 if ($MIDI_event_channel & 0x80) { 130 // OK, normal event - MIDI command has MSB set 131 $LastIssuedMIDIcommand = $MIDI_event_channel >> 4; 132 $LastIssuedMIDIchannel = $MIDI_event_channel & 0x0F; 133 } else { 134 // running event - assume last command 135 $eventsoffset--; 136 } 137 $MIDIevents[$tracknumber][$eventid]['eventid'] = $LastIssuedMIDIcommand; 138 $MIDIevents[$tracknumber][$eventid]['channel'] = $LastIssuedMIDIchannel; 139 if ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x08) { // Note off (key is released) 140 141 $notenumber = ord(substr($trackdata, $eventsoffset++, 1)); 142 $velocity = ord(substr($trackdata, $eventsoffset++, 1)); 143 144 } elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x09) { // Note on (key is pressed) 145 146 $notenumber = ord(substr($trackdata, $eventsoffset++, 1)); 147 $velocity = ord(substr($trackdata, $eventsoffset++, 1)); 148 149 } elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0A) { // Key after-touch 150 151 $notenumber = ord(substr($trackdata, $eventsoffset++, 1)); 152 $velocity = ord(substr($trackdata, $eventsoffset++, 1)); 153 154 } elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0B) { // Control Change 155 156 $controllernum = ord(substr($trackdata, $eventsoffset++, 1)); 157 $newvalue = ord(substr($trackdata, $eventsoffset++, 1)); 158 159 } elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0C) { // Program (patch) change 160 161 $newprogramnum = ord(substr($trackdata, $eventsoffset++, 1)); 162 163 $thisfile_midi_raw['track'][$tracknumber]['instrumentid'] = $newprogramnum; 164 if ($tracknumber == 10) { 165 $thisfile_midi_raw['track'][$tracknumber]['instrument'] = $this->GeneralMIDIpercussionLookup($newprogramnum); 166 } else { 167 $thisfile_midi_raw['track'][$tracknumber]['instrument'] = $this->GeneralMIDIinstrumentLookup($newprogramnum); 168 } 169 170 } elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0D) { // Channel after-touch 171 172 $channelnumber = ord(substr($trackdata, $eventsoffset++, 1)); 173 174 } elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0E) { // Pitch wheel change (2000H is normal or no change) 175 176 $changeLSB = ord(substr($trackdata, $eventsoffset++, 1)); 177 $changeMSB = ord(substr($trackdata, $eventsoffset++, 1)); 178 $pitchwheelchange = (($changeMSB & 0x7F) << 7) & ($changeLSB & 0x7F); 179 180 } elseif (($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0F) && ($MIDIevents[$tracknumber][$eventid]['channel'] == 0x0F)) { 181 182 $METAeventCommand = ord(substr($trackdata, $eventsoffset++, 1)); 183 $METAeventLength = ord(substr($trackdata, $eventsoffset++, 1)); 184 $METAeventData = substr($trackdata, $eventsoffset, $METAeventLength); 185 $eventsoffset += $METAeventLength; 186 switch ($METAeventCommand) { 187 case 0x00: // Set track sequence number 188 $track_sequence_number = getid3_lib::BigEndian2Int(substr($METAeventData, 0, $METAeventLength)); 189 //$thisfile_midi_raw['events'][$tracknumber][$eventid]['seqno'] = $track_sequence_number; 190 break; 191 192 case 0x01: // Text: generic 193 $text_generic = substr($METAeventData, 0, $METAeventLength); 194 //$thisfile_midi_raw['events'][$tracknumber][$eventid]['text'] = $text_generic; 195 $thisfile_midi['comments']['comment'][] = $text_generic; 196 break; 197 198 case 0x02: // Text: copyright 199 $text_copyright = substr($METAeventData, 0, $METAeventLength); 200 //$thisfile_midi_raw['events'][$tracknumber][$eventid]['copyright'] = $text_copyright; 201 $thisfile_midi['comments']['copyright'][] = $text_copyright; 202 break; 203 204 case 0x03: // Text: track name 205 $text_trackname = substr($METAeventData, 0, $METAeventLength); 206 $thisfile_midi_raw['track'][$tracknumber]['name'] = $text_trackname; 207 break; 208 209 case 0x04: // Text: track instrument name 210 $text_instrument = substr($METAeventData, 0, $METAeventLength); 211 //$thisfile_midi_raw['events'][$tracknumber][$eventid]['instrument'] = $text_instrument; 212 break; 213 214 case 0x05: // Text: lyrics 215 $text_lyrics = substr($METAeventData, 0, $METAeventLength); 216 //$thisfile_midi_raw['events'][$tracknumber][$eventid]['lyrics'] = $text_lyrics; 217 if (!isset($thisfile_midi['lyrics'])) { 218 $thisfile_midi['lyrics'] = ''; 219 } 220 $thisfile_midi['lyrics'] .= $text_lyrics."\n"; 221 break; 222 223 case 0x06: // Text: marker 224 $text_marker = substr($METAeventData, 0, $METAeventLength); 225 //$thisfile_midi_raw['events'][$tracknumber][$eventid]['marker'] = $text_marker; 226 break; 227 228 case 0x07: // Text: cue point 229 $text_cuepoint = substr($METAeventData, 0, $METAeventLength); 230 //$thisfile_midi_raw['events'][$tracknumber][$eventid]['cuepoint'] = $text_cuepoint; 231 break; 232 233 case 0x2F: // End Of Track 234 //$thisfile_midi_raw['events'][$tracknumber][$eventid]['EOT'] = $CumulativeDeltaTime; 235 break; 236 237 case 0x51: // Tempo: microseconds / quarter note 238 $CurrentMicroSecondsPerBeat = getid3_lib::BigEndian2Int(substr($METAeventData, 0, $METAeventLength)); 239 if ($CurrentMicroSecondsPerBeat == 0) { 240 $this->error('Corrupt MIDI file: CurrentMicroSecondsPerBeat == zero'); 241 return false; 242 } 243 $thisfile_midi_raw['events'][$tracknumber][$CumulativeDeltaTime]['us_qnote'] = $CurrentMicroSecondsPerBeat; 244 $CurrentBeatsPerMinute = (1000000 / $CurrentMicroSecondsPerBeat) * 60; 245 $MicroSecondsPerQuarterNoteAfter[$CumulativeDeltaTime] = $CurrentMicroSecondsPerBeat; 246 $TicksAtCurrentBPM = 0; 247 break; 248 249 case 0x58: // Time signature 250 $timesig_numerator = getid3_lib::BigEndian2Int($METAeventData[0]); 251 $timesig_denominator = pow(2, getid3_lib::BigEndian2Int($METAeventData[1])); // $02 -> x/4, $03 -> x/8, etc 252 $timesig_32inqnote = getid3_lib::BigEndian2Int($METAeventData[2]); // number of 32nd notes to the quarter note 253 //$thisfile_midi_raw['events'][$tracknumber][$eventid]['timesig_32inqnote'] = $timesig_32inqnote; 254 //$thisfile_midi_raw['events'][$tracknumber][$eventid]['timesig_numerator'] = $timesig_numerator; 255 //$thisfile_midi_raw['events'][$tracknumber][$eventid]['timesig_denominator'] = $timesig_denominator; 256 //$thisfile_midi_raw['events'][$tracknumber][$eventid]['timesig_text'] = $timesig_numerator.'/'.$timesig_denominator; 257 $thisfile_midi['timesignature'][] = $timesig_numerator.'/'.$timesig_denominator; 258 break; 259 260 case 0x59: // Keysignature 261 $keysig_sharpsflats = getid3_lib::BigEndian2Int($METAeventData[0]); 262 if ($keysig_sharpsflats & 0x80) { 263 // (-7 -> 7 flats, 0 ->key of C, 7 -> 7 sharps) 264 $keysig_sharpsflats -= 256; 265 } 266 267 $keysig_majorminor = getid3_lib::BigEndian2Int($METAeventData[1]); // 0 -> major, 1 -> minor 268 $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#'); 269 //$thisfile_midi_raw['events'][$tracknumber][$eventid]['keysig_sharps'] = (($keysig_sharpsflats > 0) ? abs($keysig_sharpsflats) : 0); 270 //$thisfile_midi_raw['events'][$tracknumber][$eventid]['keysig_flats'] = (($keysig_sharpsflats < 0) ? abs($keysig_sharpsflats) : 0); 271 //$thisfile_midi_raw['events'][$tracknumber][$eventid]['keysig_minor'] = (bool) $keysig_majorminor; 272 //$thisfile_midi_raw['events'][$tracknumber][$eventid]['keysig_text'] = $keysigs[$keysig_sharpsflats].' '.($thisfile_midi_raw['events'][$tracknumber][$eventid]['keysig_minor'] ? 'minor' : 'major'); 273 274 // $keysigs[$keysig_sharpsflats] gets an int key (correct) - $keysigs["$keysig_sharpsflats"] gets a string key (incorrect) 275 $thisfile_midi['keysignature'][] = $keysigs[$keysig_sharpsflats].' '.((bool) $keysig_majorminor ? 'minor' : 'major'); 276 break; 277 278 case 0x7F: // Sequencer specific information 279 $custom_data = substr($METAeventData, 0, $METAeventLength); 280 break; 281 282 default: 283 $this->warning('Unhandled META Event Command: '.$METAeventCommand); 284 break; 285 } 286 287 } else { 288 289 $this->warning('Unhandled MIDI Event ID: '.$MIDIevents[$tracknumber][$eventid]['eventid'].' + Channel ID: '.$MIDIevents[$tracknumber][$eventid]['channel']); 290 291 } 292 } 293 if (($tracknumber > 0) || (count($trackdataarray) == 1)) { 294 $thisfile_midi['totalticks'] = max($thisfile_midi['totalticks'], $CumulativeDeltaTime); 295 } 296 } 297 $previoustickoffset = null; 298 $prevmicrosecondsperbeat = null; 299 300 ksort($MicroSecondsPerQuarterNoteAfter); 301 foreach ($MicroSecondsPerQuarterNoteAfter as $tickoffset => $microsecondsperbeat) { 302 if (is_null($previoustickoffset)) { 303 $prevmicrosecondsperbeat = $microsecondsperbeat; 304 $previoustickoffset = $tickoffset; 305 continue; 306 } 307 if ($thisfile_midi['totalticks'] > $tickoffset) { 308 309 if ($thisfile_midi_raw['ticksperqnote'] == 0) { 310 $this->error('Corrupt MIDI file: ticksperqnote == zero'); 311 return false; 312 } 313 314 $info['playtime_seconds'] += (($tickoffset - $previoustickoffset) / $thisfile_midi_raw['ticksperqnote']) * ($prevmicrosecondsperbeat / 1000000); 315 316 $prevmicrosecondsperbeat = $microsecondsperbeat; 317 $previoustickoffset = $tickoffset; 318 } 319 } 320 if ($thisfile_midi['totalticks'] > $previoustickoffset) { 321 322 if ($thisfile_midi_raw['ticksperqnote'] == 0) { 323 $this->error('Corrupt MIDI file: ticksperqnote == zero'); 324 return false; 325 } 326 327 $info['playtime_seconds'] += (($thisfile_midi['totalticks'] - $previoustickoffset) / $thisfile_midi_raw['ticksperqnote']) * ($prevmicrosecondsperbeat / 1000000); 328 329 } 330 } 331 332 333 if (!empty($info['playtime_seconds'])) { 334 $info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds']; 335 } 336 337 if (!empty($thisfile_midi['lyrics'])) { 338 $thisfile_midi['comments']['lyrics'][] = $thisfile_midi['lyrics']; 339 } 340 341 return true; 342 } 343 344 /** 345 * @param int $instrumentid 346 * 347 * @return string 348 */ 349 public function GeneralMIDIinstrumentLookup($instrumentid) { 350 351 $begin = __LINE__; 352 353 /** This is not a comment! 354 355 0 Acoustic Grand 356 1 Bright Acoustic 357 2 Electric Grand 358 3 Honky-Tonk 359 4 Electric Piano 1 360 5 Electric Piano 2 361 6 Harpsichord 362 7 Clavier 363 8 Celesta 364 9 Glockenspiel 365 10 Music Box 366 11 Vibraphone 367 12 Marimba 368 13 Xylophone 369 14 Tubular Bells 370 15 Dulcimer 371 16 Drawbar Organ 372 17 Percussive Organ 373 18 Rock Organ 374 19 Church Organ 375 20 Reed Organ 376 21 Accordian 377 22 Harmonica 378 23 Tango Accordian 379 24 Acoustic Guitar (nylon) 380 25 Acoustic Guitar (steel) 381 26 Electric Guitar (jazz) 382 27 Electric Guitar (clean) 383 28 Electric Guitar (muted) 384 29 Overdriven Guitar 385 30 Distortion Guitar 386 31 Guitar Harmonics 387 32 Acoustic Bass 388 33 Electric Bass (finger) 389 34 Electric Bass (pick) 390 35 Fretless Bass 391 36 Slap Bass 1 392 37 Slap Bass 2 393 38 Synth Bass 1 394 39 Synth Bass 2 395 40 Violin 396 41 Viola 397 42 Cello 398 43 Contrabass 399 44 Tremolo Strings 400 45 Pizzicato Strings 401 46 Orchestral Strings 402 47 Timpani 403 48 String Ensemble 1 404 49 String Ensemble 2 405 50 SynthStrings 1 406 51 SynthStrings 2 407 52 Choir Aahs 408 53 Voice Oohs 409 54 Synth Voice 410 55 Orchestra Hit 411 56 Trumpet 412 57 Trombone 413 58 Tuba 414 59 Muted Trumpet 415 60 French Horn 416 61 Brass Section 417 62 SynthBrass 1 418 63 SynthBrass 2 419 64 Soprano Sax 420 65 Alto Sax 421 66 Tenor Sax 422 67 Baritone Sax 423 68 Oboe 424 69 English Horn 425 70 Bassoon 426 71 Clarinet 427 72 Piccolo 428 73 Flute 429 74 Recorder 430 75 Pan Flute 431 76 Blown Bottle 432 77 Shakuhachi 433 78 Whistle 434 79 Ocarina 435 80 Lead 1 (square) 436 81 Lead 2 (sawtooth) 437 82 Lead 3 (calliope) 438 83 Lead 4 (chiff) 439 84 Lead 5 (charang) 440 85 Lead 6 (voice) 441 86 Lead 7 (fifths) 442 87 Lead 8 (bass + lead) 443 88 Pad 1 (new age) 444 89 Pad 2 (warm) 445 90 Pad 3 (polysynth) 446 91 Pad 4 (choir) 447 92 Pad 5 (bowed) 448 93 Pad 6 (metallic) 449 94 Pad 7 (halo) 450 95 Pad 8 (sweep) 451 96 FX 1 (rain) 452 97 FX 2 (soundtrack) 453 98 FX 3 (crystal) 454 99 FX 4 (atmosphere) 455 100 FX 5 (brightness) 456 101 FX 6 (goblins) 457 102 FX 7 (echoes) 458 103 FX 8 (sci-fi) 459 104 Sitar 460 105 Banjo 461 106 Shamisen 462 107 Koto 463 108 Kalimba 464 109 Bagpipe 465 110 Fiddle 466 111 Shanai 467 112 Tinkle Bell 468 113 Agogo 469 114 Steel Drums 470 115 Woodblock 471 116 Taiko Drum 472 117 Melodic Tom 473 118 Synth Drum 474 119 Reverse Cymbal 475 120 Guitar Fret Noise 476 121 Breath Noise 477 122 Seashore 478 123 Bird Tweet 479 124 Telephone Ring 480 125 Helicopter 481 126 Applause 482 127 Gunshot 483 484 */ 485 486 return getid3_lib::EmbeddedLookup($instrumentid, $begin, __LINE__, __FILE__, 'GeneralMIDIinstrument'); 487 } 488 489 /** 490 * @param int $instrumentid 491 * 492 * @return string 493 */ 494 public function GeneralMIDIpercussionLookup($instrumentid) { 495 496 $begin = __LINE__; 497 498 /** This is not a comment! 499 500 35 Acoustic Bass Drum 501 36 Bass Drum 1 502 37 Side Stick 503 38 Acoustic Snare 504 39 Hand Clap 505 40 Electric Snare 506 41 Low Floor Tom 507 42 Closed Hi-Hat 508 43 High Floor Tom 509 44 Pedal Hi-Hat 510 45 Low Tom 511 46 Open Hi-Hat 512 47 Low-Mid Tom 513 48 Hi-Mid Tom 514 49 Crash Cymbal 1 515 50 High Tom 516 51 Ride Cymbal 1 517 52 Chinese Cymbal 518 53 Ride Bell 519 54 Tambourine 520 55 Splash Cymbal 521 56 Cowbell 522 57 Crash Cymbal 2 523 59 Ride Cymbal 2 524 60 Hi Bongo 525 61 Low Bongo 526 62 Mute Hi Conga 527 63 Open Hi Conga 528 64 Low Conga 529 65 High Timbale 530 66 Low Timbale 531 67 High Agogo 532 68 Low Agogo 533 69 Cabasa 534 70 Maracas 535 71 Short Whistle 536 72 Long Whistle 537 73 Short Guiro 538 74 Long Guiro 539 75 Claves 540 76 Hi Wood Block 541 77 Low Wood Block 542 78 Mute Cuica 543 79 Open Cuica 544 80 Mute Triangle 545 81 Open Triangle 546 547 */ 548 549 return getid3_lib::EmbeddedLookup($instrumentid, $begin, __LINE__, __FILE__, 'GeneralMIDIpercussion'); 550 } 551 552} 553