PageRenderTime 227ms CodeModel.GetById 101ms app.highlight 41ms RepoModel.GetById 79ms app.codeStats 1ms

/getid3/module.audio.midi.php

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