rlm@3: | rlm@3: // | Allan Hansen | rlm@3: // +----------------------------------------------------------------------+ rlm@3: // | module.audio.midi.php | rlm@3: // | Module for analyzing midi audio files | rlm@3: // | dependencies: NONE | rlm@3: // +----------------------------------------------------------------------+ rlm@3: // rlm@3: // $Id: module.audio.midi.php,v 1.5 2006/11/02 10:48:01 ah Exp $ rlm@3: rlm@3: rlm@3: rlm@3: class getid3_midi extends getid3_handler rlm@3: { rlm@3: rlm@3: public function Analyze() { rlm@3: rlm@3: $getid3 = $this->getid3; rlm@3: rlm@3: $getid3->info['midi']['raw'] = array (); rlm@3: $info_midi = &$getid3->info['midi']; rlm@3: $info_midi_raw = &$info_midi['raw']; rlm@3: rlm@3: $getid3->info['fileformat'] = 'midi'; rlm@3: $getid3->info['audio']['dataformat'] = 'midi'; rlm@3: rlm@3: fseek($getid3->fp, $getid3->info['avdataoffset'], SEEK_SET); rlm@3: $midi_data = fread($getid3->fp, getid3::FREAD_BUFFER_SIZE); rlm@3: rlm@3: // Magic bytes: 'MThd' rlm@3: rlm@3: getid3_lib::ReadSequence('BigEndian2Int', $info_midi_raw, $midi_data, 4, rlm@3: array ( rlm@3: 'headersize' => 4, rlm@3: 'fileformat' => 2, rlm@3: 'tracks' => 2, rlm@3: 'ticksperqnote' => 2 rlm@3: ) rlm@3: ); rlm@3: rlm@3: $offset = 14; rlm@3: rlm@3: for ($i = 0; $i < $info_midi_raw['tracks']; $i++) { rlm@3: rlm@3: if ((strlen($midi_data) - $offset) < 8) { rlm@3: $midi_data .= fread($getid3->fp, getid3::FREAD_BUFFER_SIZE); rlm@3: } rlm@3: rlm@3: $track_id = substr($midi_data, $offset, 4); rlm@3: $offset += 4; rlm@3: rlm@3: if ($track_id != 'MTrk') { rlm@3: throw new getid3_exception('Expecting "MTrk" at '.$offset.', found '.$track_id.' instead'); rlm@3: } rlm@3: rlm@3: $track_size = getid3_lib::BigEndian2Int(substr($midi_data, $offset, 4)); rlm@3: $offset += 4; rlm@3: rlm@3: $track_data_array[$i] = substr($midi_data, $offset, $track_size); rlm@3: $offset += $track_size; rlm@3: } rlm@3: rlm@3: if (!isset($track_data_array) || !is_array($track_data_array)) { rlm@3: throw new getid3_exception('Cannot find MIDI track information'); rlm@3: } rlm@3: rlm@3: rlm@3: $info_midi['totalticks'] = 0; rlm@3: $getid3->info['playtime_seconds'] = 0; rlm@3: $current_ms_per_beat = 500000; // 120 beats per minute; 60,000,000 microseconds per minute -> 500,000 microseconds per beat rlm@3: $current_beats_per_min = 120; // 120 beats per minute; 60,000,000 microseconds per minute -> 500,000 microseconds per beat rlm@3: $ms_per_quarter_note_after = array (); rlm@3: rlm@3: foreach ($track_data_array as $track_number => $track_data) { rlm@3: rlm@3: $events_offset = $last_issued_midi_command = $last_issued_midi_channel = $cumulative_delta_time = $ticks_at_current_bpm = 0; rlm@3: rlm@3: while ($events_offset < strlen($track_data)) { rlm@3: rlm@3: $event_id = 0; rlm@3: if (isset($midi_events[$track_number]) && is_array($midi_events[$track_number])) { rlm@3: $event_id = count($midi_events[$track_number]); rlm@3: } rlm@3: $delta_time = 0; rlm@3: for ($i = 0; $i < 4; $i++) { rlm@3: $delta_time_byte = ord($track_data{$events_offset++}); rlm@3: $delta_time = ($delta_time << 7) + ($delta_time_byte & 0x7F); rlm@3: if ($delta_time_byte & 0x80) { rlm@3: // another byte follows rlm@3: } else { rlm@3: break; rlm@3: } rlm@3: } rlm@3: rlm@3: $cumulative_delta_time += $delta_time; rlm@3: $ticks_at_current_bpm += $delta_time; rlm@3: rlm@3: $midi_events[$track_number][$event_id]['deltatime'] = $delta_time; rlm@3: rlm@3: $midi_event_channel = ord($track_data{$events_offset++}); rlm@3: rlm@3: // OK, normal event - MIDI command has MSB set rlm@3: if ($midi_event_channel & 0x80) { rlm@3: $last_issued_midi_command = $midi_event_channel >> 4; rlm@3: $last_issued_midi_channel = $midi_event_channel & 0x0F; rlm@3: } rlm@3: rlm@3: // Running event - assume last command rlm@3: else { rlm@3: $events_offset--; rlm@3: } rlm@3: rlm@3: $midi_events[$track_number][$event_id]['eventid'] = $last_issued_midi_command; rlm@3: $midi_events[$track_number][$event_id]['channel'] = $last_issued_midi_channel; rlm@3: rlm@3: switch ($midi_events[$track_number][$event_id]['eventid']) { rlm@3: rlm@3: case 0x8: // Note off (key is released) rlm@3: case 0x9: // Note on (key is pressed) rlm@3: case 0xA: // Key after-touch rlm@3: rlm@3: //$notenumber = ord($track_data{$events_offset++}); rlm@3: //$velocity = ord($track_data{$events_offset++}); rlm@3: $events_offset += 2; rlm@3: break; rlm@3: rlm@3: rlm@3: case 0xB: // Control Change rlm@3: rlm@3: //$controllernum = ord($track_data{$events_offset++}); rlm@3: //$newvalue = ord($track_data{$events_offset++}); rlm@3: $events_offset += 2; rlm@3: break; rlm@3: rlm@3: rlm@3: case 0xC: // Program (patch) change rlm@3: rlm@3: $new_program_num = ord($track_data{$events_offset++}); rlm@3: rlm@3: $info_midi_raw['track'][$track_number]['instrumentid'] = $new_program_num; rlm@3: $info_midi_raw['track'][$track_number]['instrument'] = $track_number == 10 ? getid3_midi::GeneralMIDIpercussionLookup($new_program_num) : getid3_midi::GeneralMIDIinstrumentLookup($new_program_num); rlm@3: break; rlm@3: rlm@3: rlm@3: case 0xD: // Channel after-touch rlm@3: rlm@3: //$channelnumber = ord($track_data{$events_offset++}); rlm@3: break; rlm@3: rlm@3: rlm@3: case 0xE: // Pitch wheel change (2000H is normal or no change) rlm@3: rlm@3: //$changeLSB = ord($track_data{$events_offset++}); rlm@3: //$changeMSB = ord($track_data{$events_offset++}); rlm@3: //$pitchwheelchange = (($changeMSB & 0x7F) << 7) & ($changeLSB & 0x7F); rlm@3: $events_offset += 2; rlm@3: break; rlm@3: rlm@3: rlm@3: case 0xF: rlm@3: rlm@3: if ($midi_events[$track_number][$event_id]['channel'] == 0xF) { rlm@3: rlm@3: $meta_event_command = ord($track_data{$events_offset++}); rlm@3: $meta_event_length = ord($track_data{$events_offset++}); rlm@3: $meta_event_data = substr($track_data, $events_offset, $meta_event_length); rlm@3: $events_offset += $meta_event_length; rlm@3: rlm@3: switch ($meta_event_command) { rlm@3: rlm@3: case 0x00: // Set track sequence number rlm@3: rlm@3: //$track_sequence_number = getid3_lib::BigEndian2Int(substr($meta_event_data, 0, $meta_event_length)); rlm@3: //$info_midi_raw['events'][$track_number][$event_id]['seqno'] = $track_sequence_number; rlm@3: break; rlm@3: rlm@3: rlm@3: case 0x01: // Text: generic rlm@3: rlm@3: $text_generic = substr($meta_event_data, 0, $meta_event_length); rlm@3: //$info_midi_raw['events'][$track_number][$event_id]['text'] = $text_generic; rlm@3: $info_midi['comments']['comment'][] = $text_generic; rlm@3: break; rlm@3: rlm@3: rlm@3: case 0x02: // Text: copyright rlm@3: rlm@3: $text_copyright = substr($meta_event_data, 0, $meta_event_length); rlm@3: //$info_midi_raw['events'][$track_number][$event_id]['copyright'] = $text_copyright; rlm@3: $info_midi['comments']['copyright'][] = $text_copyright; rlm@3: break; rlm@3: rlm@3: rlm@3: case 0x03: // Text: track name rlm@3: rlm@3: $text_trackname = substr($meta_event_data, 0, $meta_event_length); rlm@3: $info_midi_raw['track'][$track_number]['name'] = $text_trackname; rlm@3: break; rlm@3: rlm@3: rlm@3: case 0x04: // Text: track instrument name rlm@3: rlm@3: //$text_instrument = substr($meta_event_data, 0, $meta_event_length); rlm@3: //$info_midi_raw['events'][$track_number][$event_id]['instrument'] = $text_instrument; rlm@3: break; rlm@3: rlm@3: rlm@3: case 0x05: // Text: lyrics rlm@3: rlm@3: $text_lyrics = substr($meta_event_data, 0, $meta_event_length); rlm@3: //$info_midi_raw['events'][$track_number][$event_id]['lyrics'] = $text_lyrics; rlm@3: if (!isset($info_midi['lyrics'])) { rlm@3: $info_midi['lyrics'] = ''; rlm@3: } rlm@3: $info_midi['lyrics'] .= $text_lyrics . "\n"; rlm@3: break; rlm@3: rlm@3: rlm@3: case 0x06: // Text: marker rlm@3: rlm@3: //$text_marker = substr($meta_event_data, 0, $meta_event_length); rlm@3: //$info_midi_raw['events'][$track_number][$event_id]['marker'] = $text_marker; rlm@3: break; rlm@3: rlm@3: rlm@3: case 0x07: // Text: cue point rlm@3: rlm@3: //$text_cuepoint = substr($meta_event_data, 0, $meta_event_length); rlm@3: //$info_midi_raw['events'][$track_number][$event_id]['cuepoint'] = $text_cuepoint; rlm@3: break; rlm@3: rlm@3: rlm@3: case 0x2F: // End Of Track rlm@3: rlm@3: //$info_midi_raw['events'][$track_number][$event_id]['EOT'] = $cumulative_delta_time; rlm@3: break; rlm@3: rlm@3: rlm@3: case 0x51: // Tempo: microseconds / quarter note rlm@3: rlm@3: $current_ms_per_beat = getid3_lib::BigEndian2Int(substr($meta_event_data, 0, $meta_event_length)); rlm@3: $info_midi_raw['events'][$track_number][$cumulative_delta_time]['us_qnote'] = $current_ms_per_beat; rlm@3: $current_beats_per_min = (1000000 / $current_ms_per_beat) * 60; rlm@3: $ms_per_quarter_note_after[$cumulative_delta_time] = $current_ms_per_beat; rlm@3: $ticks_at_current_bpm = 0; rlm@3: break; rlm@3: rlm@3: rlm@3: case 0x58: // Time signature rlm@3: $timesig_numerator = getid3_lib::BigEndian2Int($meta_event_data[0]); rlm@3: $timesig_denominator = pow(2, getid3_lib::BigEndian2Int($meta_event_data[1])); // $02 -> x/4, $03 -> x/8, etc rlm@3: //$timesig_32inqnote = getid3_lib::BigEndian2Int($meta_event_data[2]); // number of 32nd notes to the quarter note rlm@3: //$info_midi_raw['events'][$track_number][$event_id]['timesig_32inqnote'] = $timesig_32inqnote; rlm@3: //$info_midi_raw['events'][$track_number][$event_id]['timesig_numerator'] = $timesig_numerator; rlm@3: //$info_midi_raw['events'][$track_number][$event_id]['timesig_denominator'] = $timesig_denominator; rlm@3: //$info_midi_raw['events'][$track_number][$event_id]['timesig_text'] = $timesig_numerator.'/'.$timesig_denominator; rlm@3: $info_midi['timesignature'][] = $timesig_numerator.'/'.$timesig_denominator; rlm@3: break; rlm@3: rlm@3: rlm@3: case 0x59: // Keysignature rlm@3: rlm@3: $keysig_sharpsflats = getid3_lib::BigEndian2Int($meta_event_data{0}); rlm@3: if ($keysig_sharpsflats & 0x80) { rlm@3: // (-7 -> 7 flats, 0 ->key of C, 7 -> 7 sharps) rlm@3: $keysig_sharpsflats -= 256; rlm@3: } rlm@3: rlm@3: $keysig_majorminor = getid3_lib::BigEndian2Int($meta_event_data{1}); // 0 -> major, 1 -> minor rlm@3: $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#'); rlm@3: //$info_midi_raw['events'][$track_number][$event_id]['keysig_sharps'] = (($keysig_sharpsflats > 0) ? abs($keysig_sharpsflats) : 0); rlm@3: //$info_midi_raw['events'][$track_number][$event_id]['keysig_flats'] = (($keysig_sharpsflats < 0) ? abs($keysig_sharpsflats) : 0); rlm@3: //$info_midi_raw['events'][$track_number][$event_id]['keysig_minor'] = (bool)$keysig_majorminor; rlm@3: //$info_midi_raw['events'][$track_number][$event_id]['keysig_text'] = $keysigs[$keysig_sharpsflats].' '.($info_midi_raw['events'][$track_number][$event_id]['keysig_minor'] ? 'minor' : 'major'); rlm@3: rlm@3: // $keysigs[$keysig_sharpsflats] gets an int key (correct) - $keysigs["$keysig_sharpsflats"] gets a string key (incorrect) rlm@3: $info_midi['keysignature'][] = $keysigs[$keysig_sharpsflats].' '.((bool)$keysig_majorminor ? 'minor' : 'major'); rlm@3: break; rlm@3: rlm@3: rlm@3: case 0x7F: // Sequencer specific information rlm@3: rlm@3: $custom_data = substr($meta_event_data, 0, $meta_event_length); rlm@3: break; rlm@3: rlm@3: rlm@3: default: rlm@3: rlm@3: $getid3->warning('Unhandled META Event Command: '.$meta_event_command); rlm@3: } rlm@3: } rlm@3: break; rlm@3: rlm@3: rlm@3: default: rlm@3: $getid3->warning('Unhandled MIDI Event ID: '.$midi_events[$track_number][$event_id]['eventid']); rlm@3: } rlm@3: } rlm@3: rlm@3: if (($track_number > 0) || (count($track_data_array) == 1)) { rlm@3: $info_midi['totalticks'] = max($info_midi['totalticks'], $cumulative_delta_time); rlm@3: } rlm@3: } rlm@3: rlm@3: $previous_tick_offset = null; rlm@3: rlm@3: ksort($ms_per_quarter_note_after); rlm@3: foreach ($ms_per_quarter_note_after as $tick_offset => $ms_per_beat) { rlm@3: rlm@3: if (is_null($previous_tick_offset)) { rlm@3: $prev_ms_per_beat = $ms_per_beat; rlm@3: $previous_tick_offset = $tick_offset; rlm@3: continue; rlm@3: } rlm@3: rlm@3: if ($info_midi['totalticks'] > $tick_offset) { rlm@3: $getid3->info['playtime_seconds'] += (($tick_offset - $previous_tick_offset) / $info_midi_raw['ticksperqnote']) * ($prev_ms_per_beat / 1000000); rlm@3: rlm@3: $prev_ms_per_beat = $ms_per_beat; rlm@3: $previous_tick_offset = $tick_offset; rlm@3: } rlm@3: } rlm@3: rlm@3: if ($info_midi['totalticks'] > $previous_tick_offset) { rlm@3: $getid3->info['playtime_seconds'] += (($info_midi['totalticks'] - $previous_tick_offset) / $info_midi_raw['ticksperqnote']) * ($ms_per_beat / 1000000); rlm@3: } rlm@3: rlm@3: if (@$getid3->info['playtime_seconds'] > 0) { rlm@3: $getid3->info['bitrate'] = (($getid3->info['avdataend'] - $getid3->info['avdataoffset']) * 8) / $getid3->info['playtime_seconds']; rlm@3: } rlm@3: rlm@3: if (!empty($info_midi['lyrics'])) { rlm@3: $info_midi['comments']['lyrics'][] = $info_midi['lyrics']; rlm@3: } rlm@3: rlm@3: return true; rlm@3: } rlm@3: rlm@3: rlm@3: rlm@3: public static function GeneralMIDIinstrumentLookup($instrument_id) { rlm@3: rlm@3: static $lookup = array ( rlm@3: rlm@3: 0 => 'Acoustic Grand', rlm@3: 1 => 'Bright Acoustic', rlm@3: 2 => 'Electric Grand', rlm@3: 3 => 'Honky-Tonk', rlm@3: 4 => 'Electric Piano 1', rlm@3: 5 => 'Electric Piano 2', rlm@3: 6 => 'Harpsichord', rlm@3: 7 => 'Clavier', rlm@3: 8 => 'Celesta', rlm@3: 9 => 'Glockenspiel', rlm@3: 10 => 'Music Box', rlm@3: 11 => 'Vibraphone', rlm@3: 12 => 'Marimba', rlm@3: 13 => 'Xylophone', rlm@3: 14 => 'Tubular Bells', rlm@3: 15 => 'Dulcimer', rlm@3: 16 => 'Drawbar Organ', rlm@3: 17 => 'Percussive Organ', rlm@3: 18 => 'Rock Organ', rlm@3: 19 => 'Church Organ', rlm@3: 20 => 'Reed Organ', rlm@3: 21 => 'Accordian', rlm@3: 22 => 'Harmonica', rlm@3: 23 => 'Tango Accordian', rlm@3: 24 => 'Acoustic Guitar (nylon)', rlm@3: 25 => 'Acoustic Guitar (steel)', rlm@3: 26 => 'Electric Guitar (jazz)', rlm@3: 27 => 'Electric Guitar (clean)', rlm@3: 28 => 'Electric Guitar (muted)', rlm@3: 29 => 'Overdriven Guitar', rlm@3: 30 => 'Distortion Guitar', rlm@3: 31 => 'Guitar Harmonics', rlm@3: 32 => 'Acoustic Bass', rlm@3: 33 => 'Electric Bass (finger)', rlm@3: 34 => 'Electric Bass (pick)', rlm@3: 35 => 'Fretless Bass', rlm@3: 36 => 'Slap Bass 1', rlm@3: 37 => 'Slap Bass 2', rlm@3: 38 => 'Synth Bass 1', rlm@3: 39 => 'Synth Bass 2', rlm@3: 40 => 'Violin', rlm@3: 41 => 'Viola', rlm@3: 42 => 'Cello', rlm@3: 43 => 'Contrabass', rlm@3: 44 => 'Tremolo Strings', rlm@3: 45 => 'Pizzicato Strings', rlm@3: 46 => 'Orchestral Strings', rlm@3: 47 => 'Timpani', rlm@3: 48 => 'String Ensemble 1', rlm@3: 49 => 'String Ensemble 2', rlm@3: 50 => 'SynthStrings 1', rlm@3: 51 => 'SynthStrings 2', rlm@3: 52 => 'Choir Aahs', rlm@3: 53 => 'Voice Oohs', rlm@3: 54 => 'Synth Voice', rlm@3: 55 => 'Orchestra Hit', rlm@3: 56 => 'Trumpet', rlm@3: 57 => 'Trombone', rlm@3: 58 => 'Tuba', rlm@3: 59 => 'Muted Trumpet', rlm@3: 60 => 'French Horn', rlm@3: 61 => 'Brass Section', rlm@3: 62 => 'SynthBrass 1', rlm@3: 63 => 'SynthBrass 2', rlm@3: 64 => 'Soprano Sax', rlm@3: 65 => 'Alto Sax', rlm@3: 66 => 'Tenor Sax', rlm@3: 67 => 'Baritone Sax', rlm@3: 68 => 'Oboe', rlm@3: 69 => 'English Horn', rlm@3: 70 => 'Bassoon', rlm@3: 71 => 'Clarinet', rlm@3: 72 => 'Piccolo', rlm@3: 73 => 'Flute', rlm@3: 74 => 'Recorder', rlm@3: 75 => 'Pan Flute', rlm@3: 76 => 'Blown Bottle', rlm@3: 77 => 'Shakuhachi', rlm@3: 78 => 'Whistle', rlm@3: 79 => 'Ocarina', rlm@3: 80 => 'Lead 1 (square)', rlm@3: 81 => 'Lead 2 (sawtooth)', rlm@3: 82 => 'Lead 3 (calliope)', rlm@3: 83 => 'Lead 4 (chiff)', rlm@3: 84 => 'Lead 5 (charang)', rlm@3: 85 => 'Lead 6 (voice)', rlm@3: 86 => 'Lead 7 (fifths)', rlm@3: 87 => 'Lead 8 (bass + lead)', rlm@3: 88 => 'Pad 1 (new age)', rlm@3: 89 => 'Pad 2 (warm)', rlm@3: 90 => 'Pad 3 (polysynth)', rlm@3: 91 => 'Pad 4 (choir)', rlm@3: 92 => 'Pad 5 (bowed)', rlm@3: 93 => 'Pad 6 (metallic)', rlm@3: 94 => 'Pad 7 (halo)', rlm@3: 95 => 'Pad 8 (sweep)', rlm@3: 96 => 'FX 1 (rain)', rlm@3: 97 => 'FX 2 (soundtrack)', rlm@3: 98 => 'FX 3 (crystal)', rlm@3: 99 => 'FX 4 (atmosphere)', rlm@3: 100 => 'FX 5 (brightness)', rlm@3: 101 => 'FX 6 (goblins)', rlm@3: 102 => 'FX 7 (echoes)', rlm@3: 103 => 'FX 8 (sci-fi)', rlm@3: 104 => 'Sitar', rlm@3: 105 => 'Banjo', rlm@3: 106 => 'Shamisen', rlm@3: 107 => 'Koto', rlm@3: 108 => 'Kalimba', rlm@3: 109 => 'Bagpipe', rlm@3: 110 => 'Fiddle', rlm@3: 111 => 'Shanai', rlm@3: 112 => 'Tinkle Bell', rlm@3: 113 => 'Agogo', rlm@3: 114 => 'Steel Drums', rlm@3: 115 => 'Woodblock', rlm@3: 116 => 'Taiko Drum', rlm@3: 117 => 'Melodic Tom', rlm@3: 118 => 'Synth Drum', rlm@3: 119 => 'Reverse Cymbal', rlm@3: 120 => 'Guitar Fret Noise', rlm@3: 121 => 'Breath Noise', rlm@3: 122 => 'Seashore', rlm@3: 123 => 'Bird Tweet', rlm@3: 124 => 'Telephone Ring', rlm@3: 125 => 'Helicopter', rlm@3: 126 => 'Applause', rlm@3: 127 => 'Gunshot' rlm@3: ); rlm@3: rlm@3: return @$lookup[$instrument_id]; rlm@3: } rlm@3: rlm@3: rlm@3: rlm@3: public static function GeneralMIDIpercussionLookup($instrument_id) { rlm@3: rlm@3: static $lookup = array ( rlm@3: rlm@3: 35 => 'Acoustic Bass Drum', rlm@3: 36 => 'Bass Drum 1', rlm@3: 37 => 'Side Stick', rlm@3: 38 => 'Acoustic Snare', rlm@3: 39 => 'Hand Clap', rlm@3: 40 => 'Electric Snare', rlm@3: 41 => 'Low Floor Tom', rlm@3: 42 => 'Closed Hi-Hat', rlm@3: 43 => 'High Floor Tom', rlm@3: 44 => 'Pedal Hi-Hat', rlm@3: 45 => 'Low Tom', rlm@3: 46 => 'Open Hi-Hat', rlm@3: 47 => 'Low-Mid Tom', rlm@3: 48 => 'Hi-Mid Tom', rlm@3: 49 => 'Crash Cymbal 1', rlm@3: 50 => 'High Tom', rlm@3: 51 => 'Ride Cymbal 1', rlm@3: 52 => 'Chinese Cymbal', rlm@3: 53 => 'Ride Bell', rlm@3: 54 => 'Tambourine', rlm@3: 55 => 'Splash Cymbal', rlm@3: 56 => 'Cowbell', rlm@3: 57 => 'Crash Cymbal 2', rlm@3: 59 => 'Ride Cymbal 2', rlm@3: 60 => 'Hi Bongo', rlm@3: 61 => 'Low Bongo', rlm@3: 62 => 'Mute Hi Conga', rlm@3: 63 => 'Open Hi Conga', rlm@3: 64 => 'Low Conga', rlm@3: 65 => 'High Timbale', rlm@3: 66 => 'Low Timbale', rlm@3: 67 => 'High Agogo', rlm@3: 68 => 'Low Agogo', rlm@3: 69 => 'Cabasa', rlm@3: 70 => 'Maracas', rlm@3: 71 => 'Short Whistle', rlm@3: 72 => 'Long Whistle', rlm@3: 73 => 'Short Guiro', rlm@3: 74 => 'Long Guiro', rlm@3: 75 => 'Claves', rlm@3: 76 => 'Hi Wood Block', rlm@3: 77 => 'Low Wood Block', rlm@3: 78 => 'Mute Cuica', rlm@3: 79 => 'Open Cuica', rlm@3: 80 => 'Mute Triangle', rlm@3: 81 => 'Open Triangle' rlm@3: ); rlm@3: rlm@3: return @$lookup[$instrument_id]; rlm@3: } rlm@3: rlm@3: rlm@3: } rlm@3: rlm@3: rlm@3: ?>