[SPIP] ~maj v3.2.9-->v3.2.11
[lhc/web/www.git] / www / plugins-dist / medias / lib / getid3 / module.audio.midi.php
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
17 if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
18 exit;
19 }
20
21 define('GETID3_MIDI_MAGIC_MTHD', 'MThd'); // MIDI file header magic
22 define('GETID3_MIDI_MAGIC_MTRK', 'MTrk'); // MIDI track header magic
23
24 class 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 }