ILIAS  release_5-1 Revision 5.0.0-5477-g43f3e3fab5f
module.audio.midi.php
Go to the documentation of this file.
1<?php
4// available at http://getid3.sourceforge.net //
5// or http://www.getid3.org //
7// See readme.txt for more details //
9// //
10// module.audio.midi.php //
11// module for Midi Audio files //
12// dependencies: NONE //
13// ///
15
16
18{
19
20 function getid3_midi(&$fd, &$ThisFileInfo, $scanwholefile=true) {
21
22 // shortcut
23 $ThisFileInfo['midi']['raw'] = array();
24 $thisfile_midi = &$ThisFileInfo['midi'];
25 $thisfile_midi_raw = &$thisfile_midi['raw'];
26
27 $ThisFileInfo['fileformat'] = 'midi';
28 $ThisFileInfo['audio']['dataformat'] = 'midi';
29
30 fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET);
31 $MIDIdata = fread($fd, GETID3_FREAD_BUFFER_SIZE);
32 $offset = 0;
33 $MIDIheaderID = substr($MIDIdata, $offset, 4); // 'MThd'
34 if ($MIDIheaderID != 'MThd') {
35 $ThisFileInfo['error'][] = 'Expecting "MThd" at offset '.$ThisFileInfo['avdataoffset'].', found "'.$MIDIheaderID.'"';
36 unset($ThisFileInfo['fileformat']);
37 return false;
38 }
39 $offset += 4;
40 $thisfile_midi_raw['headersize'] = getid3_lib::BigEndian2Int(substr($MIDIdata, $offset, 4));
41 $offset += 4;
42 $thisfile_midi_raw['fileformat'] = getid3_lib::BigEndian2Int(substr($MIDIdata, $offset, 2));
43 $offset += 2;
44 $thisfile_midi_raw['tracks'] = getid3_lib::BigEndian2Int(substr($MIDIdata, $offset, 2));
45 $offset += 2;
46 $thisfile_midi_raw['ticksperqnote'] = getid3_lib::BigEndian2Int(substr($MIDIdata, $offset, 2));
47 $offset += 2;
48
49 for ($i = 0; $i < $thisfile_midi_raw['tracks']; $i++) {
50 if ((strlen($MIDIdata) - $offset) < 8) {
51 $MIDIdata .= fread($fd, GETID3_FREAD_BUFFER_SIZE);
52 }
53 $trackID = substr($MIDIdata, $offset, 4);
54 $offset += 4;
55 if ($trackID == 'MTrk') {
56 $tracksize = getid3_lib::BigEndian2Int(substr($MIDIdata, $offset, 4));
57 $offset += 4;
58 // $thisfile_midi['tracks'][$i]['size'] = $tracksize;
59 $trackdataarray[$i] = substr($MIDIdata, $offset, $tracksize);
60 $offset += $tracksize;
61 } else {
62 $ThisFileInfo['error'][] = 'Expecting "MTrk" at '.$offset.', found '.$trackID.' instead';
63 return false;
64 }
65 }
66
67 if (!isset($trackdataarray) || !is_array($trackdataarray)) {
68 $ThisFileInfo['error'][] = 'Cannot find MIDI track information';
69 unset($thisfile_midi);
70 unset($ThisFileInfo['fileformat']);
71 return false;
72 }
73
74 if ($scanwholefile) { // this can take quite a long time, so have the option to bypass it if speed is very important
75 $thisfile_midi['totalticks'] = 0;
76 $ThisFileInfo['playtime_seconds'] = 0;
77 $CurrentMicroSecondsPerBeat = 500000; // 120 beats per minute; 60,000,000 microseconds per minute -> 500,000 microseconds per beat
78 $CurrentBeatsPerMinute = 120; // 120 beats per minute; 60,000,000 microseconds per minute -> 500,000 microseconds per beat
79
80 foreach ($trackdataarray as $tracknumber => $trackdata) {
81
82 $eventsoffset = 0;
83 $LastIssuedMIDIcommand = 0;
84 $LastIssuedMIDIchannel = 0;
85 $CumulativeDeltaTime = 0;
86 $TicksAtCurrentBPM = 0;
87 while ($eventsoffset < strlen($trackdata)) {
88 $eventid = 0;
89 if (isset($MIDIevents[$tracknumber]) && is_array($MIDIevents[$tracknumber])) {
90 $eventid = count($MIDIevents[$tracknumber]);
91 }
92 $deltatime = 0;
93 for ($i = 0; $i < 4; $i++) {
94 $deltatimebyte = ord(substr($trackdata, $eventsoffset++, 1));
95 $deltatime = ($deltatime << 7) + ($deltatimebyte & 0x7F);
96 if ($deltatimebyte & 0x80) {
97 // another byte follows
98 } else {
99 break;
100 }
101 }
102 $CumulativeDeltaTime += $deltatime;
103 $TicksAtCurrentBPM += $deltatime;
104 $MIDIevents[$tracknumber][$eventid]['deltatime'] = $deltatime;
105 $MIDI_event_channel = ord(substr($trackdata, $eventsoffset++, 1));
106 if ($MIDI_event_channel & 0x80) {
107 // OK, normal event - MIDI command has MSB set
108 $LastIssuedMIDIcommand = $MIDI_event_channel >> 4;
109 $LastIssuedMIDIchannel = $MIDI_event_channel & 0x0F;
110 } else {
111 // running event - assume last command
112 $eventsoffset--;
113 }
114 $MIDIevents[$tracknumber][$eventid]['eventid'] = $LastIssuedMIDIcommand;
115 $MIDIevents[$tracknumber][$eventid]['channel'] = $LastIssuedMIDIchannel;
116 if ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x08) { // Note off (key is released)
117
118 $notenumber = ord(substr($trackdata, $eventsoffset++, 1));
119 $velocity = ord(substr($trackdata, $eventsoffset++, 1));
120
121 } elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x09) { // Note on (key is pressed)
122
123 $notenumber = ord(substr($trackdata, $eventsoffset++, 1));
124 $velocity = ord(substr($trackdata, $eventsoffset++, 1));
125
126 } elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0A) { // Key after-touch
127
128 $notenumber = ord(substr($trackdata, $eventsoffset++, 1));
129 $velocity = ord(substr($trackdata, $eventsoffset++, 1));
130
131 } elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0B) { // Control Change
132
133 $controllernum = ord(substr($trackdata, $eventsoffset++, 1));
134 $newvalue = ord(substr($trackdata, $eventsoffset++, 1));
135
136 } elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0C) { // Program (patch) change
137
138 $newprogramnum = ord(substr($trackdata, $eventsoffset++, 1));
139
140 $thisfile_midi_raw['track'][$tracknumber]['instrumentid'] = $newprogramnum;
141 if ($tracknumber == 10) {
142 $thisfile_midi_raw['track'][$tracknumber]['instrument'] = $this->GeneralMIDIpercussionLookup($newprogramnum);
143 } else {
144 $thisfile_midi_raw['track'][$tracknumber]['instrument'] = $this->GeneralMIDIinstrumentLookup($newprogramnum);
145 }
146
147 } elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0D) { // Channel after-touch
148
149 $channelnumber = ord(substr($trackdata, $eventsoffset++, 1));
150
151 } elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0E) { // Pitch wheel change (2000H is normal or no change)
152
153 $changeLSB = ord(substr($trackdata, $eventsoffset++, 1));
154 $changeMSB = ord(substr($trackdata, $eventsoffset++, 1));
155 $pitchwheelchange = (($changeMSB & 0x7F) << 7) & ($changeLSB & 0x7F);
156
157 } elseif (($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0F) && ($MIDIevents[$tracknumber][$eventid]['channel'] == 0x0F)) {
158
159 $METAeventCommand = ord(substr($trackdata, $eventsoffset++, 1));
160 $METAeventLength = ord(substr($trackdata, $eventsoffset++, 1));
161 $METAeventData = substr($trackdata, $eventsoffset, $METAeventLength);
162 $eventsoffset += $METAeventLength;
163 switch ($METAeventCommand) {
164 case 0x00: // Set track sequence number
165 $track_sequence_number = getid3_lib::BigEndian2Int(substr($METAeventData, 0, $METAeventLength));
166 //$thisfile_midi_raw['events'][$tracknumber][$eventid]['seqno'] = $track_sequence_number;
167 break;
168
169 case 0x01: // Text: generic
170 $text_generic = substr($METAeventData, 0, $METAeventLength);
171 //$thisfile_midi_raw['events'][$tracknumber][$eventid]['text'] = $text_generic;
172 $thisfile_midi['comments']['comment'][] = $text_generic;
173 break;
174
175 case 0x02: // Text: copyright
176 $text_copyright = substr($METAeventData, 0, $METAeventLength);
177 //$thisfile_midi_raw['events'][$tracknumber][$eventid]['copyright'] = $text_copyright;
178 $thisfile_midi['comments']['copyright'][] = $text_copyright;
179 break;
180
181 case 0x03: // Text: track name
182 $text_trackname = substr($METAeventData, 0, $METAeventLength);
183 $thisfile_midi_raw['track'][$tracknumber]['name'] = $text_trackname;
184 break;
185
186 case 0x04: // Text: track instrument name
187 $text_instrument = substr($METAeventData, 0, $METAeventLength);
188 //$thisfile_midi_raw['events'][$tracknumber][$eventid]['instrument'] = $text_instrument;
189 break;
190
191 case 0x05: // Text: lyrics
192 $text_lyrics = substr($METAeventData, 0, $METAeventLength);
193 //$thisfile_midi_raw['events'][$tracknumber][$eventid]['lyrics'] = $text_lyrics;
194 if (!isset($thisfile_midi['lyrics'])) {
195 $thisfile_midi['lyrics'] = '';
196 }
197 $thisfile_midi['lyrics'] .= $text_lyrics."\n";
198 break;
199
200 case 0x06: // Text: marker
201 $text_marker = substr($METAeventData, 0, $METAeventLength);
202 //$thisfile_midi_raw['events'][$tracknumber][$eventid]['marker'] = $text_marker;
203 break;
204
205 case 0x07: // Text: cue point
206 $text_cuepoint = substr($METAeventData, 0, $METAeventLength);
207 //$thisfile_midi_raw['events'][$tracknumber][$eventid]['cuepoint'] = $text_cuepoint;
208 break;
209
210 case 0x2F: // End Of Track
211 //$thisfile_midi_raw['events'][$tracknumber][$eventid]['EOT'] = $CumulativeDeltaTime;
212 break;
213
214 case 0x51: // Tempo: microseconds / quarter note
215 $CurrentMicroSecondsPerBeat = getid3_lib::BigEndian2Int(substr($METAeventData, 0, $METAeventLength));
216 if ($CurrentMicroSecondsPerBeat == 0) {
217 $ThisFileInfo['error'][] = 'Corrupt MIDI file: CurrentMicroSecondsPerBeat == zero';
218 return false;
219 }
220 $thisfile_midi_raw['events'][$tracknumber][$CumulativeDeltaTime]['us_qnote'] = $CurrentMicroSecondsPerBeat;
221 $CurrentBeatsPerMinute = (1000000 / $CurrentMicroSecondsPerBeat) * 60;
222 $MicroSecondsPerQuarterNoteAfter[$CumulativeDeltaTime] = $CurrentMicroSecondsPerBeat;
223 $TicksAtCurrentBPM = 0;
224 break;
225
226 case 0x58: // Time signature
227 $timesig_numerator = getid3_lib::BigEndian2Int($METAeventData{0});
228 $timesig_denominator = pow(2, getid3_lib::BigEndian2Int($METAeventData{1})); // $02 -> x/4, $03 -> x/8, etc
229 $timesig_32inqnote = getid3_lib::BigEndian2Int($METAeventData{2}); // number of 32nd notes to the quarter note
230 //$thisfile_midi_raw['events'][$tracknumber][$eventid]['timesig_32inqnote'] = $timesig_32inqnote;
231 //$thisfile_midi_raw['events'][$tracknumber][$eventid]['timesig_numerator'] = $timesig_numerator;
232 //$thisfile_midi_raw['events'][$tracknumber][$eventid]['timesig_denominator'] = $timesig_denominator;
233 //$thisfile_midi_raw['events'][$tracknumber][$eventid]['timesig_text'] = $timesig_numerator.'/'.$timesig_denominator;
234 $thisfile_midi['timesignature'][] = $timesig_numerator.'/'.$timesig_denominator;
235 break;
236
237 case 0x59: // Keysignature
238 $keysig_sharpsflats = getid3_lib::BigEndian2Int($METAeventData{0});
239 if ($keysig_sharpsflats & 0x80) {
240 // (-7 -> 7 flats, 0 ->key of C, 7 -> 7 sharps)
241 $keysig_sharpsflats -= 256;
242 }
243
244 $keysig_majorminor = getid3_lib::BigEndian2Int($METAeventData{1}); // 0 -> major, 1 -> minor
245 $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#');
246 //$thisfile_midi_raw['events'][$tracknumber][$eventid]['keysig_sharps'] = (($keysig_sharpsflats > 0) ? abs($keysig_sharpsflats) : 0);
247 //$thisfile_midi_raw['events'][$tracknumber][$eventid]['keysig_flats'] = (($keysig_sharpsflats < 0) ? abs($keysig_sharpsflats) : 0);
248 //$thisfile_midi_raw['events'][$tracknumber][$eventid]['keysig_minor'] = (bool) $keysig_majorminor;
249 //$thisfile_midi_raw['events'][$tracknumber][$eventid]['keysig_text'] = $keysigs[$keysig_sharpsflats].' '.($thisfile_midi_raw['events'][$tracknumber][$eventid]['keysig_minor'] ? 'minor' : 'major');
250
251 // $keysigs[$keysig_sharpsflats] gets an int key (correct) - $keysigs["$keysig_sharpsflats"] gets a string key (incorrect)
252 $thisfile_midi['keysignature'][] = $keysigs[$keysig_sharpsflats].' '.((bool) $keysig_majorminor ? 'minor' : 'major');
253 break;
254
255 case 0x7F: // Sequencer specific information
256 $custom_data = substr($METAeventData, 0, $METAeventLength);
257 break;
258
259 default:
260 $ThisFileInfo['warning'][] = 'Unhandled META Event Command: '.$METAeventCommand;
261 break;
262 }
263
264 } else {
265
266 $ThisFileInfo['warning'][] = 'Unhandled MIDI Event ID: '.$MIDIevents[$tracknumber][$eventid]['eventid'].' + Channel ID: '.$MIDIevents[$tracknumber][$eventid]['channel'];
267
268 }
269 }
270 if (($tracknumber > 0) || (count($trackdataarray) == 1)) {
271 $thisfile_midi['totalticks'] = max($thisfile_midi['totalticks'], $CumulativeDeltaTime);
272 }
273 }
274 $previoustickoffset = null;
275
276 ksort($MicroSecondsPerQuarterNoteAfter);
277 foreach ($MicroSecondsPerQuarterNoteAfter as $tickoffset => $microsecondsperbeat) {
278 if (is_null($previoustickoffset)) {
279 $prevmicrosecondsperbeat = $microsecondsperbeat;
280 $previoustickoffset = $tickoffset;
281 continue;
282 }
283 if ($thisfile_midi['totalticks'] > $tickoffset) {
284
285 if ($thisfile_midi_raw['ticksperqnote'] == 0) {
286 $ThisFileInfo['error'][] = 'Corrupt MIDI file: ticksperqnote == zero';
287 return false;
288 }
289
290 $ThisFileInfo['playtime_seconds'] += (($tickoffset - $previoustickoffset) / $thisfile_midi_raw['ticksperqnote']) * ($prevmicrosecondsperbeat / 1000000);
291
292 $prevmicrosecondsperbeat = $microsecondsperbeat;
293 $previoustickoffset = $tickoffset;
294 }
295 }
296 if ($thisfile_midi['totalticks'] > $previoustickoffset) {
297
298 if ($thisfile_midi_raw['ticksperqnote'] == 0) {
299 $ThisFileInfo['error'][] = 'Corrupt MIDI file: ticksperqnote == zero';
300 return false;
301 }
302
303 $ThisFileInfo['playtime_seconds'] += (($thisfile_midi['totalticks'] - $previoustickoffset) / $thisfile_midi_raw['ticksperqnote']) * ($microsecondsperbeat / 1000000);
304
305 }
306 }
307
308 if ($ThisFileInfo['playtime_seconds'] > 0) {
309 $ThisFileInfo['bitrate'] = (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8) / $ThisFileInfo['playtime_seconds'];
310 }
311
312 if (!empty($thisfile_midi['lyrics'])) {
313 $thisfile_midi['comments']['lyrics'][] = $thisfile_midi['lyrics'];
314 }
315
316 return true;
317 }
318
319 function GeneralMIDIinstrumentLookup($instrumentid) {
320
321 $begin = __LINE__;
322
456 return getid3_lib::EmbeddedLookup($instrumentid, $begin, __LINE__, __FILE__, 'GeneralMIDIinstrument');
457 }
458
459 function GeneralMIDIpercussionLookup($instrumentid) {
460
461 $begin = __LINE__;
462
514 return getid3_lib::EmbeddedLookup($instrumentid, $begin, __LINE__, __FILE__, 'GeneralMIDIpercussion');
515 }
516
517}
518
519
520?>
fseek($bytes, $whence=SEEK_SET)
Definition: getid3.php:1697
fread($bytes)
Definition: getid3.php:1685
EmbeddedLookup($key, $begin, $end, $file, $name)
BigEndian2Int($byteword, $synchsafe=false, $signed=false)
Definition: getid3.lib.php:234
getID3() by James Heinrich info@getid3.org //
getid3_midi(&$fd, &$ThisFileInfo, $scanwholefile=true)
GeneralMIDIinstrumentLookup($instrumentid)
GeneralMIDIpercussionLookup($instrumentid)
const GETID3_FREAD_BUFFER_SIZE
Definition: getid3.php:14