1 package camidion.chordhelper.music;
2 import java.io.UnsupportedEncodingException;
3 import java.nio.charset.Charset;
4 import java.util.HashMap;
8 import javax.sound.midi.InvalidMidiDataException;
9 import javax.sound.midi.MetaMessage;
10 import javax.sound.midi.MidiEvent;
11 import javax.sound.midi.MidiMessage;
12 import javax.sound.midi.Sequence;
13 import javax.sound.midi.ShortMessage;
14 import javax.sound.midi.SysexMessage;
15 import javax.sound.midi.Track;
17 * MIDI仕様(システムエクスクルーシブ含む)
19 public class MIDISpec {
20 public static final int MAX_CHANNELS = 16;
21 public static final int PITCH_BEND_NONE = 8192;
23 * 指定されたMIDIノート番号の音の周波数(A=440Hzチューニング時)を返します。
24 * @param noteNumber MIDIノート番号
27 public static double noteNumberToFrequency(int noteNumber) {
28 return 55 * Math.pow( 2, (double)(noteNumber - 33)/12 );
33 private static final Map<Integer,String>
34 META_MESSAGE_TYPE_NAMES = new HashMap<Integer,String>() {
36 put(0x00, "Seq Number");
38 put(0x02, "Copyright");
39 put(0x03, "Seq/Track Name");
40 put(0x04, "Instrument Name");
43 put(0x07, "Cue Point");
44 put(0x08, "Program Name");
45 put(0x09, "Device Name");
46 put(0x20, "MIDI Ch.Prefix");
47 put(0x21, "MIDI Output Port");
48 put(0x2F, "End Of Track");
50 put(0x54, "SMPTE Offset");
51 put(0x58, "Time Signature");
52 put(0x59, "Key Signature");
53 put(0x7F, "Sequencer Specific");
58 * @param metaMessageType メタメッセージタイプ
59 * @return メタメッセージタイプの名前
61 public static String getMetaName(int metaMessageType) {
62 return META_MESSAGE_TYPE_NAMES.get(metaMessageType);
65 * メタメッセージタイプがテキストのつくものか調べます。
66 * @param metaMessageType メタメッセージタイプ
67 * @return テキストがつくときtrue
69 public static boolean hasMetaMessageText(int metaMessageType) {
70 return (metaMessageType > 0 && metaMessageType < 10);
73 * メタメッセージタイプが拍子記号か調べます。
74 * @param metaMessageType メタメッセージタイプ
77 public static boolean isTimeSignature(int metaMessageType) {
78 return metaMessageType == 0x58;
81 * MIDIメッセージが拍子記号か調べます。
82 * @param msg MIDIメッセージ
85 public static boolean isTimeSignature(MidiMessage midiMessage) {
86 if ( !(midiMessage instanceof MetaMessage) ) return false;
87 return isTimeSignature( ((MetaMessage)midiMessage).getType() );
90 * メタメッセージタイプが EOT (End Of Track) か調べます。
91 * @param metaMessageType メタメッセージタイプ
94 public static boolean isEOT(int metaMessageType) { return metaMessageType == 0x2F; }
96 * MIDIメッセージが EOT (End Of Track) か調べます。
97 * @param midiMessage MIDIメッセージ
100 public static boolean isEOT(MidiMessage midiMessage) {
101 if ( !(midiMessage instanceof MetaMessage) ) return false;
102 return isEOT( ((MetaMessage)midiMessage).getType() );
107 public static final int MICROSECOND_PER_MINUTE = (60 * 1000 * 1000);
109 * MIDIのテンポメッセージについているバイト列をQPM単位のテンポに変換します。
113 public static int byteArrayToQpmTempo(byte[] b) {
114 int tempoInUsPerQuarter = ((b[0] & 0xFF) << 16) | ((b[1] & 0xFF) << 8) | (b[2] & 0xFF);
115 return MICROSECOND_PER_MINUTE / tempoInUsPerQuarter;
118 * QPM単位のテンポをMIDIのテンポメッセージ用バイト列に変換します。
119 * @param qpm テンポ[QPM]
120 * @return MIDIのテンポメッセージ用バイト列
122 public static byte[] qpmTempoToByteArray(int qpm) {
123 int tempoInUsPerQuarter = MICROSECOND_PER_MINUTE / qpm;
124 byte[] b = new byte[3];
125 b[0] = (byte)((tempoInUsPerQuarter >> 16) & 0xFF);
126 b[1] = (byte)((tempoInUsPerQuarter >> 8) & 0xFF);
127 b[2] = (byte)(tempoInUsPerQuarter & 0xFF);
132 * @param track MIDIトラック
135 public static byte[] getNameBytesOf(Track track) {
138 MetaMessage metaMessage;
139 for( int i=0; i<track.size(); i++ ) {
140 midiEvent = track.get(i);
141 if( midiEvent.getTick() > 0 ) { // No more event at top, try next track
144 message = midiEvent.getMessage();
145 if( ! (message instanceof MetaMessage) ) { // Not meta message
148 metaMessage = (MetaMessage)message;
149 if( metaMessage.getType() != 0x03 ) { // Not sequence name
152 return metaMessage.getData();
158 * @param track MIDIトラック
160 * @return 成功:true、失敗:false
162 public static boolean setNameBytesOf(Track track, byte[] name) {
163 MidiEvent midiEvent = null;
164 MidiMessage msg = null;
165 MetaMessage metaMsg = null;
166 for( int i=0; i<track.size(); i++ ) {
168 (midiEvent = track.get(i)).getTick() > 0
170 (msg = midiEvent.getMessage()) instanceof MetaMessage
172 (metaMsg = (MetaMessage)msg).getType() == 0x03
178 if( metaMsg == null ) {
179 if( name.length == 0 ) return false;
180 track.add(new MidiEvent(
181 (MidiMessage)(metaMsg = new MetaMessage()), 0
185 metaMsg.setMessage(0x03, name, name.length);
187 catch( InvalidMidiDataException e ) {
195 * <p>トラック名の入った最初のトラックにあるトラック名を
198 * @param seq MIDIシーケンス
199 * @return シーケンス名のバイト列
201 public static byte[] getNameBytesOf(Sequence seq) {
202 // Returns name of the MIDI sequence.
203 // A sequence name is placed at top of first track of the sequence.
205 Track tracks[] = seq.getTracks();
207 for( Track track : tracks ) if( (b = getNameBytesOf(track)) != null ) return b;
213 * 設定に失敗した場合、順に次のトラックへの設定を試みます。
216 * @param seq MIDIシーケンス
217 * @param name シーケンス名のバイト列
218 * @return 成功:true、失敗:false
220 public static boolean setNameBytesOf(Sequence seq, byte[] name) {
221 Track tracks[] = seq.getTracks();
222 for( Track track : tracks ) if( setNameBytesOf(track,name) ) return true;
226 * シーケンスの名前や歌詞など、メタイベントのテキストをもとに文字コードを判定します。
227 * 判定できなかった場合はnullを返します。
228 * @param seq MIDIシーケンス
229 * @return 文字コード判定結果(またはnull)
231 public static Charset getCharsetOf(Sequence seq) {
232 Track tracks[] = seq.getTracks();
233 byte[] b = new byte[0];
234 for( Track track : tracks ) {
236 MetaMessage metaMessage;
237 for( int i=0; i<track.size(); i++ ) {
238 message = track.get(i).getMessage();
239 if( ! (message instanceof MetaMessage) ) continue;
240 metaMessage = (MetaMessage)message;
241 if( ! hasMetaMessageText(metaMessage.getType()) ) continue;
242 byte[] additional = metaMessage.getData();
243 byte[] concated = new byte[b.length + additional.length];
244 System.arraycopy(b, 0, concated, 0, b.length);
245 System.arraycopy(additional, 0, concated, b.length, additional.length);
251 String autoDetectedName = new String(b, "JISAutoDetect");
252 Set<Map.Entry<String,Charset>> entrySet;
253 entrySet = Charset.availableCharsets().entrySet();
254 for( Map.Entry<String,Charset> entry : entrySet ) {
255 Charset cs = entry.getValue();
256 if( autoDetectedName.equals(new String(b, cs)) ) return cs;
258 } catch (UnsupportedEncodingException e) {
264 ///////////////////////////////////////////////////////////////////
266 // Channel Message / System Message
270 * @param status MIDIステータス
273 public static String getStatusName( int status ) {
274 if( status < 0x80 ) {
278 else if ( status < 0xF0 ) {
280 return ch_msg_status_names[ (status >> 4) - 0x08 ];
282 else if ( status <= 0xFF ) {
284 return sys_msg_names[ status - 0xF0 ];
289 * 指定のMIDIショートメッセージがチャンネルメッセージかどうか調べます。
290 * @param msg MIDIメッセージ
291 * @return MIDIチャンネルメッセージの場合true
293 public static boolean isChannelMessage( ShortMessage msg ) {
294 return isChannelMessage( msg.getStatus() );
297 * MIDIステータスがチャンネルメッセージかどうか調べます。
298 * @param status MIDIステータス
299 * @return MIDIチャンネルメッセージの場合true
301 public static boolean isChannelMessage( int status ) {
302 return ( status < 0xF0 && status >= 0x80 );
304 private static final String ch_msg_status_names[] = {
305 // 0x80 - 0xE0 : Channel Voice Message
306 // 0xB0 : Channel Mode Message
308 "Polyphonic Key Pressure", "Ctrl/Mode",
309 "Program", "Ch.Pressure", "Pitch Bend"
311 private static final String sys_msg_names[] = {
312 // 0xF0 : System Exclusive
315 // 0xF1 - 0xF7 : System Common Message
316 "MIDI Time Code Quarter Frame",
317 "Song Position Pointer", "Song Select",
318 null, null, "Tune Request", "Special SysEx",
320 // 0xF8 - 0xFF : System Realtime Message
321 // 0xFF : Meta Message (SMF only, Not for wired MIDI message)
322 "Timing Clock", null, "Start", "Continue",
323 "Stop", null, "Active Sensing", "Meta / Sys.Reset",
325 ///////////////////////////////////////////////////////////////////
327 // Control Change / Channel Mode Message
330 * コントロールチェンジの名前を返します。
331 * @param controllerNumber コントローラ番号
332 * @return コントロールチェンジの名前
334 public static String getControllerName( int controllerNumber ) {
335 if( controllerNumber < 0x00 ) {
338 else if( controllerNumber < 0x20 ) {
339 String s = controllerNames0[controllerNumber];
340 if( s != null ) s += " (MSB)";
343 else if( controllerNumber < 0x40 ) {
344 String s = controllerNames0[controllerNumber - 0x20];
345 if( s != null ) s += " (LSB)";
348 else if( controllerNumber < 0x78 ) {
349 return controllerMomentarySwitchNames[controllerNumber - 0x40];
351 else if( controllerNumber < 0x80 ) {
352 return controllerModeMessageNames[controllerNumber - 0x78];
358 private static final String controllerNames0[] = {
361 "Bank Select", "Modulation Depth", "Breath Controller", null,
362 "Foot Controller", "Portamento Time", "Data Entry", "Volume",
363 "Balance", null, "Pan", "Expression",
364 "Effect Control 1", "Effect Control 2", null, null,
367 "General Purpose 1", "General Purpose 2",
368 "General Purpose 3", "General Purpose 4",
369 null, null, null, null,
370 null, null, null, null,
371 null, null, null, null,
375 private static final String controllerMomentarySwitchNames[] = {
378 "Damper Pedal (Sustain)", "Portamento",
379 "Sustenuto", "Soft Pedal",
380 "Legato Footswitch", "Hold 2",
381 "Sound Controller 1 (Sound Variation)",
382 "Sound Controller 2 (Timbre/Harmonic Intens)",
383 "Sound Controller 3 (Release Time)",
384 "Sound Controller 4 (Attack Time)",
385 "Sound Controller 5 (Brightness)",
386 "Sound Controller 6 (Decay Time)",
387 "Sound Controller 7 (Vibrato Rate)",
388 "Sound Controller 8 (Vibrato Depth)",
389 "Sound Controller 9 (Vibrato Delay)",
390 "Sound Controller 10 (Undefined)",
393 "General Purpose 5", "General Purpose 6 (Temp Change)",
394 "General Purpose 7", "General Purpose 8",
395 "Portamento Control", null, null, null,
396 null, null, null, "Reverb (Ext.Effects Depth)",
397 "Tremelo Depth", "Chorus Depth",
398 "Celeste (Detune) Depth", "Phaser Depth",
401 "Data Increment", "Data Decrement",
402 "NRPN (LSB)", "NRPN (MSB)",
403 "RPN (LSB)", "RPN (MSB)", null, null,
404 null, null, null, null,
405 null, null, null, null,
408 null, null, null, null,
409 null, null, null, null
411 private static final String controllerModeMessageNames[] = {
413 "All Sound OFF", "Reset All Controllers",
414 "Local Control", "All Notes OFF",
415 "Omni Mode OFF", "Omni Mode ON",
416 "Mono Mode ON", "Poly Mode ON"
418 ///////////////////////////////////////////////////////////////////
423 * システムエクスクルーシブの製造者IDをキーにして製造者名を返すマップ
425 public static final Map<Integer,String>
426 SYSEX_MANUFACTURER_NAMES = new HashMap<Integer,String>() {
433 put(0x7D,"Non-Commercial");
434 put(0x7E,"Universal: Non-RealTime");
435 put(0x7F,"Universal: RealTime");
441 public static final int MAX_NOTE_NO = 127;
443 * General MIDI の楽器ファミリー名の配列
445 public static final String instrumentFamilyNames[] = {
466 * General MIDI の楽器名(プログラムチェンジのプログラム名)の配列
468 public static final String instrumentNames[] = {
469 "Acoustic Grand Piano",
470 "Bright Acoustic Piano",
471 "Electric Grand Piano",
493 "Acoustic Guitar (nylon)",
494 "Acoustic Guitar (steel)",
495 "Electric Guitar (jazz)",
496 "Electric Guitar (clean)",
497 "Electric Guitar (muted)",
502 "Electric Bass (finger)",
503 "Electric Bass (pick)",
556 "Lead 8 (bass + lead)",
599 * パーカッション用MIDIノート番号の最小値
601 public static final int MIN_PERCUSSION_NUMBER = 35;
603 * パーカッション用のMIDIチャンネル(通常はCH.10)における
604 * ノート番号からパーカッション名を返します。
606 * @param note_no ノート番号
609 public static String getPercussionName(int note_no) {
610 int i = note_no - MIN_PERCUSSION_NUMBER ;
611 return i>=0 && i < PERCUSSION_NAMES.length ? PERCUSSION_NAMES[i] : "(Unknown)" ;
613 public static final String PERCUSSION_NAMES[] = {
614 "Acoustic Bass Drum",
662 public static final String nsx39LyricElements[] = {
668 "さ","すぃ","す","せ","そ",
669 "ざ","ずぃ","ず","ぜ","ぞ",
670 "しゃ","し","しゅ","しぇ","しょ",
671 "じゃ","じ","じゅ","じぇ","じょ",
672 "た","てぃ","とぅ","て","と",
673 "だ","でぃ","どぅ","で","ど",
675 "ちゃ","ち","ちゅ","ちぇ","ちょ",
676 "つぁ","つぃ","つ","つぇ","つぉ",
685 "ふぁ","ふぃ","ふゅ","ふぇ","ふぉ",
695 * MIDIメッセージの内容を文字列で返します。
696 * @param msg MIDIメッセージ
697 * @param charset MIDIメタメッセージに含まれるテキストデータの文字コード
698 * @return MIDIメッセージの内容を表す文字列
700 public static String msgToString(MidiMessage msg, Charset charset) {
702 if( msg instanceof ShortMessage ) {
703 ShortMessage shortmsg = (ShortMessage)msg;
704 int status = msg.getStatus();
705 String statusName = getStatusName(status);
706 int data1 = shortmsg.getData1();
707 int data2 = shortmsg.getData2();
708 if( isChannelMessage(status) ) {
709 int channel = shortmsg.getChannel();
710 String channelPrefix = "Ch."+(channel+1) + ": ";
711 String statusPrefix = (
712 statusName == null ? String.format("status=0x%02X",status) : statusName
714 int cmd = shortmsg.getCommand();
716 case ShortMessage.NOTE_OFF:
717 case ShortMessage.NOTE_ON:
718 str += channelPrefix + statusPrefix + data1;
720 if( MIDISpec.isRhythmPart(channel) ) {
721 str += getPercussionName(data1);
724 str += Note.noteNumberToSymbol(data1);
726 str +="] Velocity=" + data2;
728 case ShortMessage.POLY_PRESSURE:
729 str += channelPrefix + statusPrefix + "Note=" + data1 + " Pressure=" + data2;
731 case ShortMessage.PROGRAM_CHANGE:
732 str += channelPrefix + statusPrefix + data1 + ":[" + instrumentNames[data1] + "]";
733 if( data2 != 0 ) str += " data2=" + data2;
735 case ShortMessage.CHANNEL_PRESSURE:
736 str += channelPrefix + statusPrefix + data1;
737 if( data2 != 0 ) str += " data2=" + data2;
739 case ShortMessage.PITCH_BEND:
741 int val = ((data1 & 0x7F) | ((data2 & 0x7F) << 7));
742 str += channelPrefix + statusPrefix + ( (val-8192) * 100 / 8191) + "% (" + val + ")";
745 case ShortMessage.CONTROL_CHANGE:
747 // Control / Mode message name
748 String ctrl_name = getControllerName(data1);
749 str += channelPrefix + (data1 < 0x78 ? "CtrlChg: " : "ModeMsg: ");
750 if( ctrl_name == null ) {
751 str += " No.=" + data1 + " Value=" + data2;
756 // Controller's value
758 case 0x40: case 0x41: case 0x42: case 0x43: case 0x45:
759 str += " " + ( data2==0x3F?"OFF":data2==0x40?"ON":data2 );
761 case 0x44: // Legato Footswitch
762 str += " " + ( data2==0x3F?"Normal":data2==0x40?"Legato":data2 );
764 case 0x7A: // Local Control
765 str += " " + ( data2==0x00?"OFF":data2==0x7F?"ON":data2 );
775 // Never reached here
779 else { // System Message
780 str += (statusName == null ? ("status="+status) : statusName );
781 str += " (" + data1 + "," + data2 + ")";
785 else if( msg instanceof MetaMessage ) {
786 MetaMessage metamsg = (MetaMessage)msg;
787 byte[] msgdata = metamsg.getData();
788 int msgtype = metamsg.getType();
790 String meta_name = getMetaName(msgtype);
791 if( meta_name == null ) {
792 str += "Unknown MessageType="+msgtype + " Values=(";
793 for( byte b : msgdata ) str += String.format( " %02X", b );
797 // Add the message type name
801 if( hasMetaMessageText(msgtype) ) {
802 str +=" ["+(new String(msgdata,charset))+"]";
805 // Add the numeric data
807 case 0x00: // Sequence Number (for MIDI Format 2)
808 if( msgdata.length == 2 ) {
809 str += String.format(
811 ((msgdata[0] & 0xFF) << 8) | (msgdata[1] & 0xFF)
815 str += ": Size not 2 byte : data=(";
816 for( byte b : msgdata ) str += String.format( " %02X", b );
819 case 0x20: // MIDI Ch.Prefix
820 case 0x21: // MIDI Output Port
821 if( msgdata.length == 1 ) {
822 str += String.format( ": %02X", msgdata[0] & 0xFF );
825 str += ": Size not 1 byte : data=(";
826 for( byte b : msgdata ) str += String.format( " %02X", b );
830 str += ": " + byteArrayToQpmTempo( msgdata ) + "[QPM] (";
831 for( byte b : msgdata ) str += String.format( " %02X", b );
834 case 0x54: // SMPTE Offset
835 if( msgdata.length == 5 ) {
837 + (msgdata[0] & 0xFF) + ":"
838 + (msgdata[1] & 0xFF) + ":"
839 + (msgdata[2] & 0xFF) + "."
840 + (msgdata[3] & 0xFF) + "."
841 + (msgdata[4] & 0xFF);
844 str += ": Size not 5 byte : data=(";
845 for( byte b : msgdata ) str += String.format( " %02X", b );
848 case 0x58: // Time Signature
849 if( msgdata.length == 4 ) {
850 str +=": " + msgdata[0] + "/" + (1 << msgdata[1]);
851 str +=", "+msgdata[2]+"[clk/beat], "+msgdata[3]+"[32nds/24clk]";
854 str += ": Size not 4 byte : data=(";
855 for( byte b : msgdata ) str += String.format( " %02X", b );
858 case 0x59: // Key Signature
859 if( msgdata.length == 2 ) {
860 Key key = new Key(msgdata);
861 str += ": " + key.signatureDescription();
862 str += " (" + key.toStringIn(Note.Language.NAME) + ")";
865 str += ": Size not 2 byte : data=(";
866 for( byte b : msgdata ) str += String.format( " %02X", b );
869 case 0x7F: // Sequencer Specific Meta Event
871 for( byte b : msgdata ) str += String.format( " %02X", b );
877 else if( msg instanceof SysexMessage ) {
878 SysexMessage sysexmsg = (SysexMessage)msg;
879 int status = sysexmsg.getStatus();
880 byte[] msgdata = sysexmsg.getData();
883 case SysexMessage.SYSTEM_EXCLUSIVE:
886 case SysexMessage.SPECIAL_SYSTEM_EXCLUSIVE:
887 str += "SysEx(Special): ";
890 str += "SysEx: Invalid (status="+status+") ";
893 if( msgdata.length < 1 ) {
894 str += " Invalid data size: " + msgdata.length;
897 int manufacturerId = (int)(msgdata[0] & 0xFF);
898 int deviceId = (int)(msgdata[1] & 0xFF);
899 int modelId = (int)(msgdata[2] & 0xFF);
900 String manufacturerName = SYSEX_MANUFACTURER_NAMES.get(manufacturerId);
901 if( manufacturerName == null ) {
902 manufacturerName = String.format("[Manufacturer code %02X]", msgdata[0]);
904 str += manufacturerName + String.format(" (DevID=0x%02X)", deviceId);
905 switch( manufacturerId ) {
906 case 0x7E: // Non-Realtime Universal
908 int sub_id_1 = modelId;
909 int sub_id_2 = (int)(msgdata[3] & 0xFF);
911 case 0x09: // General MIDI (GM)
913 case 0x01: str += " GM System ON"; return str;
914 case 0x02: str += " GM System OFF"; return str;
921 // case 0x7F: // Realtime Universal
926 str += " [GS]"; dataBytePos++;
927 if( msgdata[3]==0x12 ) {
928 str += "DT1:"; dataBytePos++;
929 switch( msgdata[4] ) {
931 if( msgdata[5]==0x00 ) {
932 if( msgdata[6]==0x7F ) {
933 if( msgdata[7]==0x00 ) {
934 str += " [88] System Mode Set (Mode 1: Single Module)"; return str;
936 else if( msgdata[7]==0x01 ) {
937 str += " [88] System Mode Set (Mode 2: Double Module)"; return str;
941 else if( msgdata[5]==0x01 ) {
942 int port = (msgdata[7] & 0xFF);
943 str += String.format(
944 " [88] Ch.Msg Rx Port: Block=0x%02X, Port=%s",
946 port==0?"A":port==1?"B":String.format("0x%02X",port)
952 if( msgdata[5]==0x00 ) {
953 switch( msgdata[6] ) {
954 case 0x00: str += " Master Tune: "; dataBytePos += 3; break;
955 case 0x04: str += " Master Volume: "; dataBytePos += 3; break;
956 case 0x05: str += " Master Key Shift: "; dataBytePos += 3; break;
957 case 0x06: str += " Master Pan: "; dataBytePos += 3; break;
959 switch( msgdata[7] ) {
960 case 0x00: str += " GS Reset"; return str;
961 case 0x7F: str += " Exit GS Mode"; return str;
966 else if( msgdata[5]==0x01 ) {
967 switch( msgdata[6] ) {
968 // case 0x00: str += ""; break;
969 // case 0x10: str += ""; break;
970 case 0x30: str += " Reverb Macro: "; dataBytePos += 3; break;
971 case 0x31: str += " Reverb Character: "; dataBytePos += 3; break;
972 case 0x32: str += " Reverb Pre-LPF: "; dataBytePos += 3; break;
973 case 0x33: str += " Reverb Level: "; dataBytePos += 3; break;
974 case 0x34: str += " Reverb Time: "; dataBytePos += 3; break;
975 case 0x35: str += " Reverb Delay FB: "; dataBytePos += 3; break;
976 case 0x36: str += " Reverb Chorus Level: "; dataBytePos += 3; break;
977 case 0x37: str += " [88] Reverb Predelay Time: "; dataBytePos += 3; break;
978 case 0x38: str += " Chorus Macro: "; dataBytePos += 3; break;
979 case 0x39: str += " Chorus Pre-LPF: "; dataBytePos += 3; break;
980 case 0x3A: str += " Chorus Level: "; dataBytePos += 3; break;
981 case 0x3B: str += " Chorus FB: "; dataBytePos += 3; break;
982 case 0x3C: str += " Chorus Delay: "; dataBytePos += 3; break;
983 case 0x3D: str += " Chorus Rate: "; dataBytePos += 3; break;
984 case 0x3E: str += " Chorus Depth: "; dataBytePos += 3; break;
985 case 0x3F: str += " Chorus Send Level To Reverb: "; dataBytePos += 3; break;
986 case 0x40: str += " [88] Chorus Send Level To Delay: "; dataBytePos += 3; break;
987 case 0x50: str += " [88] Delay Macro: "; dataBytePos += 3; break;
988 case 0x51: str += " [88] Delay Pre-LPF: "; dataBytePos += 3; break;
989 case 0x52: str += " [88] Delay Time Center: "; dataBytePos += 3; break;
990 case 0x53: str += " [88] Delay Time Ratio Left: "; dataBytePos += 3; break;
991 case 0x54: str += " [88] Delay Time Ratio Right: "; dataBytePos += 3; break;
992 case 0x55: str += " [88] Delay Level Center: "; dataBytePos += 3; break;
993 case 0x56: str += " [88] Delay Level Left: "; dataBytePos += 3; break;
994 case 0x57: str += " [88] Delay Level Right: "; dataBytePos += 3; break;
995 case 0x58: str += " [88] Delay Level: "; dataBytePos += 3; break;
996 case 0x59: str += " [88] Delay FB: "; dataBytePos += 3; break;
997 case 0x5A: str += " [88] Delay Send Level To Reverb: "; dataBytePos += 3; break;
1000 else if( msgdata[5]==0x02 ) {
1001 switch( msgdata[6] ) {
1002 case 0x00: str += " [88] EQ Low Freq: "; dataBytePos += 3; break;
1003 case 0x01: str += " [88] EQ Low Gain: "; dataBytePos += 3; break;
1004 case 0x02: str += " [88] EQ High Freq: "; dataBytePos += 3; break;
1005 case 0x03: str += " [88] EQ High Gain: "; dataBytePos += 3; break;
1008 else if( msgdata[5]==0x03 ) {
1009 if( msgdata[6] == 0x00 ) {
1010 str += " [Pro] EFX Type: "; dataBytePos += 3;
1012 else if( msgdata[6] >= 0x03 && msgdata[6] <= 0x16 ) {
1013 str += String.format(" [Pro] EFX Param %d", msgdata[6]-2 );
1016 else if( msgdata[6] == 0x17 ) {
1017 str += " [Pro] EFX Send Level To Reverb: "; dataBytePos += 3;
1019 else if( msgdata[6] == 0x18 ) {
1020 str += " [Pro] EFX Send Level To Chorus: "; dataBytePos += 3;
1022 else if( msgdata[6] == 0x19 ) {
1023 str += " [Pro] EFX Send Level To Delay: "; dataBytePos += 3;
1025 else if( msgdata[6] == 0x1B ) {
1026 str += " [Pro] EFX Ctrl Src1: "; dataBytePos += 3;
1028 else if( msgdata[6] == 0x1C ) {
1029 str += " [Pro] EFX Ctrl Depth1: "; dataBytePos += 3;
1031 else if( msgdata[6] == 0x1D ) {
1032 str += " [Pro] EFX Ctrl Src2: "; dataBytePos += 3;
1034 else if( msgdata[6] == 0x1E ) {
1035 str += " [Pro] EFX Ctrl Depth2: "; dataBytePos += 3;
1037 else if( msgdata[6] == 0x1F ) {
1038 str += " [Pro] EFX Send EQ Switch: "; dataBytePos += 3;
1041 else if( (msgdata[5] & 0xF0) == 0x10 ) {
1042 int ch = (msgdata[5] & 0x0F);
1043 if( ch <= 9 ) ch--; else if( ch == 0 ) ch = 9;
1044 if( msgdata[6]==0x02 ) {
1045 str += String.format(
1046 " Rx Ch: Part=%d(0x%02X) Ch=0x%02X", (ch+1), msgdata[5], msgdata[7]
1050 else if( msgdata[6]==0x15 ) {
1052 switch( msgdata[7] ) {
1053 case 0: map = " NormalPart"; break;
1054 case 1: map = " DrumMap1"; break;
1055 case 2: map = " DrumMap2"; break;
1056 default: map = String.format("0x%02X",msgdata[7]); break;
1058 str += String.format(
1059 " Rhythm Part: Ch=%d(0x%02X) Map=%s",
1066 else if( (msgdata[5] & 0xF0) == 0x40 ) {
1067 int ch = (msgdata[5] & 0x0F);
1068 if( ch <= 9 ) ch--; else if( ch == 0 ) ch = 9;
1069 int dt = (msgdata[7] & 0xFF);
1070 if( msgdata[6]==0x20 ) {
1071 str += String.format(
1072 " [88] EQ: Ch=%d(0x%02X) %s",
1074 dt==0 ? "OFF" : dt==1 ? "ON" : String.format("0x%02X",dt)
1077 else if( msgdata[6]==0x22 ) {
1078 str += String.format(
1079 " [Pro] Part EFX Assign: Ch=%d(0x%02X) %s",
1081 dt==0 ? "ByPass" : dt==1 ? "EFX" : String.format("0x%02X",dt)
1090 str += " [GS-LCD]"; dataBytePos++;
1091 if( msgdata[3]==0x12 ) {
1092 str += " [DT1]"; dataBytePos++;
1093 if( msgdata[4]==0x10 && msgdata[5]==0x00 && msgdata[6]==0x00 ) {
1095 str += " Disp [" +(new String(
1096 msgdata, dataBytePos, msgdata.length - dataBytePos - 2
1101 case 0x14: str += " [D-50]"; dataBytePos++; break;
1102 case 0x16: str += " [MT-32]"; dataBytePos++; break;
1105 case 0x43: // Yamaha
1106 if( (deviceId & 0xF0) == 0x10 && modelId == 0x4C ) {
1107 str += " [XG]Dev#="+(deviceId & 0x0F);
1109 if( msgdata[3]==0 && msgdata[4]==0 && msgdata[5]==0x7E && msgdata[6]==0 ) {
1110 str += " System ON";
1115 else if( deviceId == 0x79 && modelId == 9 ) {
1116 str += " [eVocaloid]";
1118 if( msgdata[3]==0x11 && msgdata[4]==0x0A && msgdata[5]==0 ) {
1119 StringBuilder p = new StringBuilder();
1120 for( int i=6; i<msgdata.length; i++ ) {
1121 int b = (msgdata[i] & 0xFF);
1122 if( b == 0xF7 ) break;
1124 b >= MIDISpec.nsx39LyricElements.length ?
1125 "?": MIDISpec.nsx39LyricElements[b]
1129 str += " pronounce["+p+"]";
1139 for( i = dataBytePos; i<msgdata.length-1; i++ ) {
1140 str += String.format( " %02X", msgdata[i] );
1142 if( i < msgdata.length && (int)(msgdata[i] & 0xFF) != 0xF7 ) {
1143 str+=" [ Invalid EOX " + String.format( "%02X", msgdata[i] ) + " ]";
1148 byte[] msg_data = msg.getMessage();
1150 for( byte b : msg_data ) {
1151 str += String.format( " %02X", b );
1156 public static boolean isRhythmPart(int ch) { return (ch == 9); }