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