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