summaryrefslogtreecommitdiff
path: root/getid3/module.audio.midi.php
diff options
context:
space:
mode:
Diffstat (limited to 'getid3/module.audio.midi.php')
-rw-r--r--getid3/module.audio.midi.php522
1 files changed, 522 insertions, 0 deletions
diff --git a/getid3/module.audio.midi.php b/getid3/module.audio.midi.php
new file mode 100644
index 0000000..0fd7c9b
--- /dev/null
+++ b/getid3/module.audio.midi.php
@@ -0,0 +1,522 @@
+<?php
+/////////////////////////////////////////////////////////////////
+/// getID3() by James Heinrich <info@getid3.org> //
+// available at http://getid3.sourceforge.net //
+// or http://www.getid3.org //
+/////////////////////////////////////////////////////////////////
+// See readme.txt for more details //
+/////////////////////////////////////////////////////////////////
+// //
+// module.audio.midi.php //
+// module for Midi Audio files //
+// dependencies: NONE //
+// ///
+/////////////////////////////////////////////////////////////////
+
+
+class getid3_midi
+{
+
+ function getid3_midi(&$fd, &$ThisFileInfo, $scanwholefile=true) {
+
+ // shortcut
+ $ThisFileInfo['midi']['raw'] = array();
+ $thisfile_midi = &$ThisFileInfo['midi'];
+ $thisfile_midi_raw = &$thisfile_midi['raw'];
+
+ $ThisFileInfo['fileformat'] = 'midi';
+ $ThisFileInfo['audio']['dataformat'] = 'midi';
+
+ fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET);
+ $MIDIdata = fread($fd, GETID3_FREAD_BUFFER_SIZE);
+ $offset = 0;
+ $MIDIheaderID = substr($MIDIdata, $offset, 4); // 'MThd'
+ if ($MIDIheaderID != 'MThd') {
+ $ThisFileInfo['error'][] = 'Expecting "MThd" at offset '.$ThisFileInfo['avdataoffset'].', found "'.$MIDIheaderID.'"';
+ unset($ThisFileInfo['fileformat']);
+ return false;
+ }
+ $offset += 4;
+ $thisfile_midi_raw['headersize'] = getid3_lib::BigEndian2Int(substr($MIDIdata, $offset, 4));
+ $offset += 4;
+ $thisfile_midi_raw['fileformat'] = getid3_lib::BigEndian2Int(substr($MIDIdata, $offset, 2));
+ $offset += 2;
+ $thisfile_midi_raw['tracks'] = getid3_lib::BigEndian2Int(substr($MIDIdata, $offset, 2));
+ $offset += 2;
+ $thisfile_midi_raw['ticksperqnote'] = getid3_lib::BigEndian2Int(substr($MIDIdata, $offset, 2));
+ $offset += 2;
+
+ for ($i = 0; $i < $thisfile_midi_raw['tracks']; $i++) {
+ if ((strlen($MIDIdata) - $offset) < 8) {
+ $MIDIdata .= fread($fd, GETID3_FREAD_BUFFER_SIZE);
+ }
+ $trackID = substr($MIDIdata, $offset, 4);
+ $offset += 4;
+ if ($trackID == 'MTrk') {
+ $tracksize = getid3_lib::BigEndian2Int(substr($MIDIdata, $offset, 4));
+ $offset += 4;
+ // $thisfile_midi['tracks'][$i]['size'] = $tracksize;
+ $trackdataarray[$i] = substr($MIDIdata, $offset, $tracksize);
+ $offset += $tracksize;
+ } else {
+ $ThisFileInfo['error'][] = 'Expecting "MTrk" at '.$offset.', found '.$trackID.' instead';
+ return false;
+ }
+ }
+
+ if (!isset($trackdataarray) || !is_array($trackdataarray)) {
+ $ThisFileInfo['error'][] = 'Cannot find MIDI track information';
+ unset($thisfile_midi);
+ unset($ThisFileInfo['fileformat']);
+ return false;
+ }
+
+ if ($scanwholefile) { // this can take quite a long time, so have the option to bypass it if speed is very important
+ $thisfile_midi['totalticks'] = 0;
+ $ThisFileInfo['playtime_seconds'] = 0;
+ $CurrentMicroSecondsPerBeat = 500000; // 120 beats per minute; 60,000,000 microseconds per minute -> 500,000 microseconds per beat
+ $CurrentBeatsPerMinute = 120; // 120 beats per minute; 60,000,000 microseconds per minute -> 500,000 microseconds per beat
+ $MicroSecondsPerQuarterNoteAfter = array ();
+
+ foreach ($trackdataarray as $tracknumber => $trackdata) {
+
+ $eventsoffset = 0;
+ $LastIssuedMIDIcommand = 0;
+ $LastIssuedMIDIchannel = 0;
+ $CumulativeDeltaTime = 0;
+ $TicksAtCurrentBPM = 0;
+ while ($eventsoffset < strlen($trackdata)) {
+ $eventid = 0;
+ if (isset($MIDIevents[$tracknumber]) && is_array($MIDIevents[$tracknumber])) {
+ $eventid = count($MIDIevents[$tracknumber]);
+ }
+ $deltatime = 0;
+ for ($i = 0; $i < 4; $i++) {
+ $deltatimebyte = ord(substr($trackdata, $eventsoffset++, 1));
+ $deltatime = ($deltatime << 7) + ($deltatimebyte & 0x7F);
+ if ($deltatimebyte & 0x80) {
+ // another byte follows
+ } else {
+ break;
+ }
+ }
+ $CumulativeDeltaTime += $deltatime;
+ $TicksAtCurrentBPM += $deltatime;
+ $MIDIevents[$tracknumber][$eventid]['deltatime'] = $deltatime;
+ $MIDI_event_channel = ord(substr($trackdata, $eventsoffset++, 1));
+ if ($MIDI_event_channel & 0x80) {
+ // OK, normal event - MIDI command has MSB set
+ $LastIssuedMIDIcommand = $MIDI_event_channel >> 4;
+ $LastIssuedMIDIchannel = $MIDI_event_channel & 0x0F;
+ } else {
+ // running event - assume last command
+ $eventsoffset--;
+ }
+ $MIDIevents[$tracknumber][$eventid]['eventid'] = $LastIssuedMIDIcommand;
+ $MIDIevents[$tracknumber][$eventid]['channel'] = $LastIssuedMIDIchannel;
+ if ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x08) { // Note off (key is released)
+
+ $notenumber = ord(substr($trackdata, $eventsoffset++, 1));
+ $velocity = ord(substr($trackdata, $eventsoffset++, 1));
+
+ } elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x09) { // Note on (key is pressed)
+
+ $notenumber = ord(substr($trackdata, $eventsoffset++, 1));
+ $velocity = ord(substr($trackdata, $eventsoffset++, 1));
+
+ } elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0A) { // Key after-touch
+
+ $notenumber = ord(substr($trackdata, $eventsoffset++, 1));
+ $velocity = ord(substr($trackdata, $eventsoffset++, 1));
+
+ } elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0B) { // Control Change
+
+ $controllernum = ord(substr($trackdata, $eventsoffset++, 1));
+ $newvalue = ord(substr($trackdata, $eventsoffset++, 1));
+
+ } elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0C) { // Program (patch) change
+
+ $newprogramnum = ord(substr($trackdata, $eventsoffset++, 1));
+
+ $thisfile_midi_raw['track'][$tracknumber]['instrumentid'] = $newprogramnum;
+ if ($tracknumber == 10) {
+ $thisfile_midi_raw['track'][$tracknumber]['instrument'] = $this->GeneralMIDIpercussionLookup($newprogramnum);
+ } else {
+ $thisfile_midi_raw['track'][$tracknumber]['instrument'] = $this->GeneralMIDIinstrumentLookup($newprogramnum);
+ }
+
+ } elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0D) { // Channel after-touch
+
+ $channelnumber = ord(substr($trackdata, $eventsoffset++, 1));
+
+ } elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0E) { // Pitch wheel change (2000H is normal or no change)
+
+ $changeLSB = ord(substr($trackdata, $eventsoffset++, 1));
+ $changeMSB = ord(substr($trackdata, $eventsoffset++, 1));
+ $pitchwheelchange = (($changeMSB & 0x7F) << 7) & ($changeLSB & 0x7F);
+
+ } elseif (($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0F) && ($MIDIevents[$tracknumber][$eventid]['channel'] == 0x0F)) {
+
+ $METAeventCommand = ord(substr($trackdata, $eventsoffset++, 1));
+ $METAeventLength = ord(substr($trackdata, $eventsoffset++, 1));
+ $METAeventData = substr($trackdata, $eventsoffset, $METAeventLength);
+ $eventsoffset += $METAeventLength;
+ switch ($METAeventCommand) {
+ case 0x00: // Set track sequence number
+ $track_sequence_number = getid3_lib::BigEndian2Int(substr($METAeventData, 0, $METAeventLength));
+ //$thisfile_midi_raw['events'][$tracknumber][$eventid]['seqno'] = $track_sequence_number;
+ break;
+
+ case 0x01: // Text: generic
+ $text_generic = substr($METAeventData, 0, $METAeventLength);
+ //$thisfile_midi_raw['events'][$tracknumber][$eventid]['text'] = $text_generic;
+ $thisfile_midi['comments']['comment'][] = $text_generic;
+ break;
+
+ case 0x02: // Text: copyright
+ $text_copyright = substr($METAeventData, 0, $METAeventLength);
+ //$thisfile_midi_raw['events'][$tracknumber][$eventid]['copyright'] = $text_copyright;
+ $thisfile_midi['comments']['copyright'][] = $text_copyright;
+ break;
+
+ case 0x03: // Text: track name
+ $text_trackname = substr($METAeventData, 0, $METAeventLength);
+ $thisfile_midi_raw['track'][$tracknumber]['name'] = $text_trackname;
+ break;
+
+ case 0x04: // Text: track instrument name
+ $text_instrument = substr($METAeventData, 0, $METAeventLength);
+ //$thisfile_midi_raw['events'][$tracknumber][$eventid]['instrument'] = $text_instrument;
+ break;
+
+ case 0x05: // Text: lyrics
+ $text_lyrics = substr($METAeventData, 0, $METAeventLength);
+ //$thisfile_midi_raw['events'][$tracknumber][$eventid]['lyrics'] = $text_lyrics;
+ if (!isset($thisfile_midi['lyrics'])) {
+ $thisfile_midi['lyrics'] = '';
+ }
+ $thisfile_midi['lyrics'] .= $text_lyrics."\n";
+ break;
+
+ case 0x06: // Text: marker
+ $text_marker = substr($METAeventData, 0, $METAeventLength);
+ //$thisfile_midi_raw['events'][$tracknumber][$eventid]['marker'] = $text_marker;
+ break;
+
+ case 0x07: // Text: cue point
+ $text_cuepoint = substr($METAeventData, 0, $METAeventLength);
+ //$thisfile_midi_raw['events'][$tracknumber][$eventid]['cuepoint'] = $text_cuepoint;
+ break;
+
+ case 0x2F: // End Of Track
+ //$thisfile_midi_raw['events'][$tracknumber][$eventid]['EOT'] = $CumulativeDeltaTime;
+ break;
+
+ case 0x51: // Tempo: microseconds / quarter note
+ $CurrentMicroSecondsPerBeat = getid3_lib::BigEndian2Int(substr($METAeventData, 0, $METAeventLength));
+ if ($CurrentMicroSecondsPerBeat == 0) {
+ $ThisFileInfo['error'][] = 'Corrupt MIDI file: CurrentMicroSecondsPerBeat == zero';
+ return false;
+ }
+ $thisfile_midi_raw['events'][$tracknumber][$CumulativeDeltaTime]['us_qnote'] = $CurrentMicroSecondsPerBeat;
+ $CurrentBeatsPerMinute = (1000000 / $CurrentMicroSecondsPerBeat) * 60;
+ $MicroSecondsPerQuarterNoteAfter[$CumulativeDeltaTime] = $CurrentMicroSecondsPerBeat;
+ $TicksAtCurrentBPM = 0;
+ break;
+
+ case 0x58: // Time signature
+ $timesig_numerator = getid3_lib::BigEndian2Int($METAeventData{0});
+ $timesig_denominator = pow(2, getid3_lib::BigEndian2Int($METAeventData{1})); // $02 -> x/4, $03 -> x/8, etc
+ $timesig_32inqnote = getid3_lib::BigEndian2Int($METAeventData{2}); // number of 32nd notes to the quarter note
+ //$thisfile_midi_raw['events'][$tracknumber][$eventid]['timesig_32inqnote'] = $timesig_32inqnote;
+ //$thisfile_midi_raw['events'][$tracknumber][$eventid]['timesig_numerator'] = $timesig_numerator;
+ //$thisfile_midi_raw['events'][$tracknumber][$eventid]['timesig_denominator'] = $timesig_denominator;
+ //$thisfile_midi_raw['events'][$tracknumber][$eventid]['timesig_text'] = $timesig_numerator.'/'.$timesig_denominator;
+ $thisfile_midi['timesignature'][] = $timesig_numerator.'/'.$timesig_denominator;
+ break;
+
+ case 0x59: // Keysignature
+ $keysig_sharpsflats = getid3_lib::BigEndian2Int($METAeventData{0});
+ if ($keysig_sharpsflats & 0x80) {
+ // (-7 -> 7 flats, 0 ->key of C, 7 -> 7 sharps)
+ $keysig_sharpsflats -= 256;
+ }
+
+ $keysig_majorminor = getid3_lib::BigEndian2Int($METAeventData{1}); // 0 -> major, 1 -> minor
+ $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#');
+ //$thisfile_midi_raw['events'][$tracknumber][$eventid]['keysig_sharps'] = (($keysig_sharpsflats > 0) ? abs($keysig_sharpsflats) : 0);
+ //$thisfile_midi_raw['events'][$tracknumber][$eventid]['keysig_flats'] = (($keysig_sharpsflats < 0) ? abs($keysig_sharpsflats) : 0);
+ //$thisfile_midi_raw['events'][$tracknumber][$eventid]['keysig_minor'] = (bool) $keysig_majorminor;
+ //$thisfile_midi_raw['events'][$tracknumber][$eventid]['keysig_text'] = $keysigs[$keysig_sharpsflats].' '.($thisfile_midi_raw['events'][$tracknumber][$eventid]['keysig_minor'] ? 'minor' : 'major');
+
+ // $keysigs[$keysig_sharpsflats] gets an int key (correct) - $keysigs["$keysig_sharpsflats"] gets a string key (incorrect)
+ $thisfile_midi['keysignature'][] = $keysigs[$keysig_sharpsflats].' '.((bool) $keysig_majorminor ? 'minor' : 'major');
+ break;
+
+ case 0x7F: // Sequencer specific information
+ $custom_data = substr($METAeventData, 0, $METAeventLength);
+ break;
+
+ default:
+ $ThisFileInfo['warning'][] = 'Unhandled META Event Command: '.$METAeventCommand;
+ break;
+ }
+
+ } else {
+
+ $ThisFileInfo['warning'][] = 'Unhandled MIDI Event ID: '.$MIDIevents[$tracknumber][$eventid]['eventid'].' + Channel ID: '.$MIDIevents[$tracknumber][$eventid]['channel'];
+
+ }
+ }
+ if (($tracknumber > 0) || (count($trackdataarray) == 1)) {
+ $thisfile_midi['totalticks'] = max($thisfile_midi['totalticks'], $CumulativeDeltaTime);
+ }
+ }
+ $previoustickoffset = null;
+
+ ksort($MicroSecondsPerQuarterNoteAfter);
+ foreach ($MicroSecondsPerQuarterNoteAfter as $tickoffset => $microsecondsperbeat) {
+ if (is_null($previoustickoffset)) {
+ $prevmicrosecondsperbeat = $microsecondsperbeat;
+ $previoustickoffset = $tickoffset;
+ continue;
+ }
+ if ($thisfile_midi['totalticks'] > $tickoffset) {
+
+ if ($thisfile_midi_raw['ticksperqnote'] == 0) {
+ $ThisFileInfo['error'][] = 'Corrupt MIDI file: ticksperqnote == zero';
+ return false;
+ }
+
+ $ThisFileInfo['playtime_seconds'] += (($tickoffset - $previoustickoffset) / $thisfile_midi_raw['ticksperqnote']) * ($prevmicrosecondsperbeat / 1000000);
+
+ $prevmicrosecondsperbeat = $microsecondsperbeat;
+ $previoustickoffset = $tickoffset;
+ }
+ }
+ if ($thisfile_midi['totalticks'] > $previoustickoffset) {
+
+ if ($thisfile_midi_raw['ticksperqnote'] == 0) {
+ $ThisFileInfo['error'][] = 'Corrupt MIDI file: ticksperqnote == zero';
+ return false;
+ }
+
+ $ThisFileInfo['playtime_seconds'] += (($thisfile_midi['totalticks'] - $previoustickoffset) / $thisfile_midi_raw['ticksperqnote']) * ($microsecondsperbeat / 1000000);
+
+ }
+ }
+
+
+ if (@$ThisFileInfo['playtime_seconds'] > 0) {
+ $ThisFileInfo['bitrate'] = (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8) / $ThisFileInfo['playtime_seconds'];
+ }
+
+ if (!empty($thisfile_midi['lyrics'])) {
+ $thisfile_midi['comments']['lyrics'][] = $thisfile_midi['lyrics'];
+ }
+
+ return true;
+ }
+
+ function GeneralMIDIinstrumentLookup($instrumentid) {
+
+ $begin = __LINE__;
+
+ /** This is not a comment!
+
+ 0 Acoustic Grand
+ 1 Bright Acoustic
+ 2 Electric Grand
+ 3 Honky-Tonk
+ 4 Electric Piano 1
+ 5 Electric Piano 2
+ 6 Harpsichord
+ 7 Clavier
+ 8 Celesta
+ 9 Glockenspiel
+ 10 Music Box
+ 11 Vibraphone
+ 12 Marimba
+ 13 Xylophone
+ 14 Tubular Bells
+ 15 Dulcimer
+ 16 Drawbar Organ
+ 17 Percussive Organ
+ 18 Rock Organ
+ 19 Church Organ
+ 20 Reed Organ
+ 21 Accordian
+ 22 Harmonica
+ 23 Tango Accordian
+ 24 Acoustic Guitar (nylon)
+ 25 Acoustic Guitar (steel)
+ 26 Electric Guitar (jazz)
+ 27 Electric Guitar (clean)
+ 28 Electric Guitar (muted)
+ 29 Overdriven Guitar
+ 30 Distortion Guitar
+ 31 Guitar Harmonics
+ 32 Acoustic Bass
+ 33 Electric Bass (finger)
+ 34 Electric Bass (pick)
+ 35 Fretless Bass
+ 36 Slap Bass 1
+ 37 Slap Bass 2
+ 38 Synth Bass 1
+ 39 Synth Bass 2
+ 40 Violin
+ 41 Viola
+ 42 Cello
+ 43 Contrabass
+ 44 Tremolo Strings
+ 45 Pizzicato Strings
+ 46 Orchestral Strings
+ 47 Timpani
+ 48 String Ensemble 1
+ 49 String Ensemble 2
+ 50 SynthStrings 1
+ 51 SynthStrings 2
+ 52 Choir Aahs
+ 53 Voice Oohs
+ 54 Synth Voice
+ 55 Orchestra Hit
+ 56 Trumpet
+ 57 Trombone
+ 58 Tuba
+ 59 Muted Trumpet
+ 60 French Horn
+ 61 Brass Section
+ 62 SynthBrass 1
+ 63 SynthBrass 2
+ 64 Soprano Sax
+ 65 Alto Sax
+ 66 Tenor Sax
+ 67 Baritone Sax
+ 68 Oboe
+ 69 English Horn
+ 70 Bassoon
+ 71 Clarinet
+ 72 Piccolo
+ 73 Flute
+ 74 Recorder
+ 75 Pan Flute
+ 76 Blown Bottle
+ 77 Shakuhachi
+ 78 Whistle
+ 79 Ocarina
+ 80 Lead 1 (square)
+ 81 Lead 2 (sawtooth)
+ 82 Lead 3 (calliope)
+ 83 Lead 4 (chiff)
+ 84 Lead 5 (charang)
+ 85 Lead 6 (voice)
+ 86 Lead 7 (fifths)
+ 87 Lead 8 (bass + lead)
+ 88 Pad 1 (new age)
+ 89 Pad 2 (warm)
+ 90 Pad 3 (polysynth)
+ 91 Pad 4 (choir)
+ 92 Pad 5 (bowed)
+ 93 Pad 6 (metallic)
+ 94 Pad 7 (halo)
+ 95 Pad 8 (sweep)
+ 96 FX 1 (rain)
+ 97 FX 2 (soundtrack)
+ 98 FX 3 (crystal)
+ 99 FX 4 (atmosphere)
+ 100 FX 5 (brightness)
+ 101 FX 6 (goblins)
+ 102 FX 7 (echoes)
+ 103 FX 8 (sci-fi)
+ 104 Sitar
+ 105 Banjo
+ 106 Shamisen
+ 107 Koto
+ 108 Kalimba
+ 109 Bagpipe
+ 110 Fiddle
+ 111 Shanai
+ 112 Tinkle Bell
+ 113 Agogo
+ 114 Steel Drums
+ 115 Woodblock
+ 116 Taiko Drum
+ 117 Melodic Tom
+ 118 Synth Drum
+ 119 Reverse Cymbal
+ 120 Guitar Fret Noise
+ 121 Breath Noise
+ 122 Seashore
+ 123 Bird Tweet
+ 124 Telephone Ring
+ 125 Helicopter
+ 126 Applause
+ 127 Gunshot
+
+ */
+
+ return getid3_lib::EmbeddedLookup($instrumentid, $begin, __LINE__, __FILE__, 'GeneralMIDIinstrument');
+ }
+
+ function GeneralMIDIpercussionLookup($instrumentid) {
+
+ $begin = __LINE__;
+
+ /** This is not a comment!
+
+ 35 Acoustic Bass Drum
+ 36 Bass Drum 1
+ 37 Side Stick
+ 38 Acoustic Snare
+ 39 Hand Clap
+ 40 Electric Snare
+ 41 Low Floor Tom
+ 42 Closed Hi-Hat
+ 43 High Floor Tom
+ 44 Pedal Hi-Hat
+ 45 Low Tom
+ 46 Open Hi-Hat
+ 47 Low-Mid Tom
+ 48 Hi-Mid Tom
+ 49 Crash Cymbal 1
+ 50 High Tom
+ 51 Ride Cymbal 1
+ 52 Chinese Cymbal
+ 53 Ride Bell
+ 54 Tambourine
+ 55 Splash Cymbal
+ 56 Cowbell
+ 57 Crash Cymbal 2
+ 59 Ride Cymbal 2
+ 60 Hi Bongo
+ 61 Low Bongo
+ 62 Mute Hi Conga
+ 63 Open Hi Conga
+ 64 Low Conga
+ 65 High Timbale
+ 66 Low Timbale
+ 67 High Agogo
+ 68 Low Agogo
+ 69 Cabasa
+ 70 Maracas
+ 71 Short Whistle
+ 72 Long Whistle
+ 73 Short Guiro
+ 74 Long Guiro
+ 75 Claves
+ 76 Hi Wood Block
+ 77 Low Wood Block
+ 78 Mute Cuica
+ 79 Open Cuica
+ 80 Mute Triangle
+ 81 Open Triangle
+
+ */
+
+ return getid3_lib::EmbeddedLookup($instrumentid, $begin, __LINE__, __FILE__, 'GeneralMIDIpercussion');
+ }
+
+}
+
+
+?> \ No newline at end of file