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;
25 private static final Map<Integer,String>
26 META_MESSAGE_TYPE_NAMES = new HashMap<Integer,String>() {
28 put(0x00, "Seq Number");
30 put(0x02, "Copyright");
31 put(0x03, "Seq/Track Name");
32 put(0x04, "Instrument Name");
35 put(0x07, "Cue Point");
36 put(0x08, "Program Name");
37 put(0x09, "Device Name");
38 put(0x20, "MIDI Ch.Prefix");
39 put(0x21, "MIDI Output Port");
40 put(0x2F, "End Of Track");
42 put(0x54, "SMPTE Offset");
43 put(0x58, "Time Signature");
44 put(0x59, "Key Signature");
45 put(0x7F, "Sequencer Specific");
50 * @param metaMessageType メタメッセージタイプ
51 * @return メタメッセージタイプの名前
53 public static String getMetaName(int metaMessageType) {
54 return META_MESSAGE_TYPE_NAMES.get(metaMessageType);
57 * メタメッセージタイプがテキストのつくものか調べます。
58 * @param metaMessageType メタメッセージタイプ
59 * @return テキストがつくときtrue
61 public static boolean hasMetaMessageText(int metaMessageType) {
62 return (metaMessageType > 0 && metaMessageType < 10);
65 * メタメッセージタイプが拍子記号か調べます。
66 * @param metaMessageType メタメッセージタイプ
69 public static boolean isTimeSignature(int metaMessageType) {
70 return metaMessageType == 0x58;
73 * MIDIメッセージが拍子記号か調べます。
74 * @param msg MIDIメッセージ
77 public static boolean isTimeSignature(MidiMessage midiMessage) {
78 if ( !(midiMessage instanceof MetaMessage) ) return false;
79 return isTimeSignature( ((MetaMessage)midiMessage).getType() );
82 * メタメッセージタイプが EOT (End Of Track) か調べます。
83 * @param metaMessageType メタメッセージタイプ
86 public static boolean isEOT(int metaMessageType) { return metaMessageType == 0x2F; }
88 * MIDIメッセージが EOT (End Of Track) か調べます。
89 * @param midiMessage MIDIメッセージ
92 public static boolean isEOT(MidiMessage midiMessage) {
93 if ( !(midiMessage instanceof MetaMessage) ) return false;
94 return isEOT( ((MetaMessage)midiMessage).getType() );
99 public static final int MICROSECOND_PER_MINUTE = (60 * 1000 * 1000);
101 * MIDIのテンポメッセージについているバイト列をQPM単位のテンポに変換します。
105 public static int byteArrayToQpmTempo(byte[] b) {
106 int tempoInUsPerQuarter = ((b[0] & 0xFF) << 16) | ((b[1] & 0xFF) << 8) | (b[2] & 0xFF);
107 return MICROSECOND_PER_MINUTE / tempoInUsPerQuarter;
110 * QPM単位のテンポをMIDIのテンポメッセージ用バイト列に変換します。
111 * @param qpm テンポ[QPM]
112 * @return MIDIのテンポメッセージ用バイト列
114 public static byte[] qpmTempoToByteArray(int qpm) {
115 int tempoInUsPerQuarter = MICROSECOND_PER_MINUTE / qpm;
116 byte[] b = new byte[3];
117 b[0] = (byte)((tempoInUsPerQuarter >> 16) & 0xFF);
118 b[1] = (byte)((tempoInUsPerQuarter >> 8) & 0xFF);
119 b[2] = (byte)(tempoInUsPerQuarter & 0xFF);
124 * @param track MIDIトラック
127 public static byte[] getNameBytesOf(Track track) {
130 MetaMessage metaMessage;
131 for( int i=0; i<track.size(); i++ ) {
132 midiEvent = track.get(i);
133 if( midiEvent.getTick() > 0 ) { // No more event at top, try next track
136 message = midiEvent.getMessage();
137 if( ! (message instanceof MetaMessage) ) { // Not meta message
140 metaMessage = (MetaMessage)message;
141 if( metaMessage.getType() != 0x03 ) { // Not sequence name
144 return metaMessage.getData();
150 * @param track MIDIトラック
152 * @return 成功:true、失敗:false
154 public static boolean setNameBytesOf(Track track, byte[] name) {
155 MidiEvent midiEvent = null;
156 MidiMessage msg = null;
157 MetaMessage metaMsg = null;
158 for( int i=0; i<track.size(); i++ ) {
160 (midiEvent = track.get(i)).getTick() > 0
162 (msg = midiEvent.getMessage()) instanceof MetaMessage
164 (metaMsg = (MetaMessage)msg).getType() == 0x03
170 if( metaMsg == null ) {
171 if( name.length == 0 ) return false;
172 track.add(new MidiEvent(
173 (MidiMessage)(metaMsg = new MetaMessage()), 0
177 metaMsg.setMessage(0x03, name, name.length);
179 catch( InvalidMidiDataException e ) {
187 * <p>トラック名の入った最初のトラックにあるトラック名を
190 * @param seq MIDIシーケンス
191 * @return シーケンス名のバイト列
193 public static byte[] getNameBytesOf(Sequence seq) {
194 // Returns name of the MIDI sequence.
195 // A sequence name is placed at top of first track of the sequence.
197 Track tracks[] = seq.getTracks();
199 for( Track track : tracks ) if( (b = getNameBytesOf(track)) != null ) return b;
205 * 設定に失敗した場合、順に次のトラックへの設定を試みます。
208 * @param seq MIDIシーケンス
209 * @param name シーケンス名のバイト列
210 * @return 成功:true、失敗:false
212 public static boolean setNameBytesOf(Sequence seq, byte[] name) {
213 Track tracks[] = seq.getTracks();
214 for( Track track : tracks ) if( setNameBytesOf(track,name) ) return true;
218 * シーケンスの名前や歌詞など、メタイベントのテキストをもとに文字コードを判定します。
219 * 判定できなかった場合はnullを返します。
220 * @param seq MIDIシーケンス
221 * @return 文字コード判定結果(またはnull)
223 public static Charset getCharsetOf(Sequence seq) {
224 Track tracks[] = seq.getTracks();
225 byte[] b = new byte[0];
226 for( Track track : tracks ) {
228 MetaMessage metaMessage;
229 for( int i=0; i<track.size(); i++ ) {
230 message = track.get(i).getMessage();
231 if( ! (message instanceof MetaMessage) ) continue;
232 metaMessage = (MetaMessage)message;
233 if( ! hasMetaMessageText(metaMessage.getType()) ) continue;
234 byte[] additional = metaMessage.getData();
235 byte[] concated = new byte[b.length + additional.length];
236 System.arraycopy(b, 0, concated, 0, b.length);
237 System.arraycopy(additional, 0, concated, b.length, additional.length);
243 String autoDetectedName = new String(b, "JISAutoDetect");
244 Set<Map.Entry<String,Charset>> entrySet;
245 entrySet = Charset.availableCharsets().entrySet();
246 for( Map.Entry<String,Charset> entry : entrySet ) {
247 Charset cs = entry.getValue();
248 if( autoDetectedName.equals(new String(b, cs)) ) return cs;
250 } catch (UnsupportedEncodingException e) {
256 ///////////////////////////////////////////////////////////////////
258 // Channel Message / System Message
262 * @param status MIDIステータス
265 public static String getStatusName( int status ) {
266 if( status < 0x80 ) {
270 else if ( status < 0xF0 ) {
272 return ch_msg_status_names[ (status >> 4) - 0x08 ];
274 else if ( status <= 0xFF ) {
276 return sys_msg_names[ status - 0xF0 ];
281 * 指定のMIDIショートメッセージがチャンネルメッセージかどうか調べます。
282 * @param msg MIDIメッセージ
283 * @return MIDIチャンネルメッセージの場合true
285 public static boolean isChannelMessage( ShortMessage msg ) {
286 return isChannelMessage( msg.getStatus() );
289 * MIDIステータスがチャンネルメッセージかどうか調べます。
290 * @param status MIDIステータス
291 * @return MIDIチャンネルメッセージの場合true
293 public static boolean isChannelMessage( int status ) {
294 return ( status < 0xF0 && status >= 0x80 );
296 private static final String ch_msg_status_names[] = {
297 // 0x80 - 0xE0 : Channel Voice Message
298 // 0xB0 : Channel Mode Message
300 "Polyphonic Key Pressure", "Ctrl/Mode",
301 "Program", "Ch.Pressure", "Pitch Bend"
303 private static final String sys_msg_names[] = {
304 // 0xF0 : System Exclusive
307 // 0xF1 - 0xF7 : System Common Message
308 "MIDI Time Code Quarter Frame",
309 "Song Position Pointer", "Song Select",
310 null, null, "Tune Request", "Special SysEx",
312 // 0xF8 - 0xFF : System Realtime Message
313 // 0xFF : Meta Message (SMF only, Not for wired MIDI message)
314 "Timing Clock", null, "Start", "Continue",
315 "Stop", null, "Active Sensing", "Meta / Sys.Reset",
317 ///////////////////////////////////////////////////////////////////
319 // Control Change / Channel Mode Message
322 * コントロールチェンジの名前を返します。
323 * @param controllerNumber コントローラ番号
324 * @return コントロールチェンジの名前
326 public static String getControllerName( int controllerNumber ) {
327 if( controllerNumber < 0x00 ) {
330 else if( controllerNumber < 0x20 ) {
331 String s = controllerNames0[controllerNumber];
332 if( s != null ) s += " (MSB)";
335 else if( controllerNumber < 0x40 ) {
336 String s = controllerNames0[controllerNumber - 0x20];
337 if( s != null ) s += " (LSB)";
340 else if( controllerNumber < 0x78 ) {
341 return controllerMomentarySwitchNames[controllerNumber - 0x40];
343 else if( controllerNumber < 0x80 ) {
344 return controllerModeMessageNames[controllerNumber - 0x78];
350 private static final String controllerNames0[] = {
353 "Bank Select", "Modulation Depth", "Breath Controller", null,
354 "Foot Controller", "Portamento Time", "Data Entry", "Volume",
355 "Balance", null, "Pan", "Expression",
356 "Effect Control 1", "Effect Control 2", null, null,
359 "General Purpose 1", "General Purpose 2",
360 "General Purpose 3", "General Purpose 4",
361 null, null, null, null,
362 null, null, null, null,
363 null, null, null, null,
367 private static final String controllerMomentarySwitchNames[] = {
370 "Damper Pedal (Sustain)", "Portamento",
371 "Sustenuto", "Soft Pedal",
372 "Legato Footswitch", "Hold 2",
373 "Sound Controller 1 (Sound Variation)",
374 "Sound Controller 2 (Timbre/Harmonic Intens)",
375 "Sound Controller 3 (Release Time)",
376 "Sound Controller 4 (Attack Time)",
377 "Sound Controller 5 (Brightness)",
378 "Sound Controller 6 (Decay Time)",
379 "Sound Controller 7 (Vibrato Rate)",
380 "Sound Controller 8 (Vibrato Depth)",
381 "Sound Controller 9 (Vibrato Delay)",
382 "Sound Controller 10 (Undefined)",
385 "General Purpose 5", "General Purpose 6 (Temp Change)",
386 "General Purpose 7", "General Purpose 8",
387 "Portamento Control", null, null, null,
388 null, null, null, "Reverb (Ext.Effects Depth)",
389 "Tremelo Depth", "Chorus Depth",
390 "Celeste (Detune) Depth", "Phaser Depth",
393 "Data Increment", "Data Decrement",
394 "NRPN (LSB)", "NRPN (MSB)",
395 "RPN (LSB)", "RPN (MSB)", null, null,
396 null, null, null, null,
397 null, null, null, null,
400 null, null, null, null,
401 null, null, null, null
403 private static final String controllerModeMessageNames[] = {
405 "All Sound OFF", "Reset All Controllers",
406 "Local Control", "All Notes OFF",
407 "Omni Mode OFF", "Omni Mode ON",
408 "Mono Mode ON", "Poly Mode ON"
410 ///////////////////////////////////////////////////////////////////
415 * システムエクスクルーシブの製造者IDをキーにして製造者名を返すマップ
417 public static final Map<Integer,String>
418 SYSEX_MANUFACTURER_NAMES = new HashMap<Integer,String>() {
425 put(0x7D,"Non-Commercial");
426 put(0x7E,"Universal: Non-RealTime");
427 put(0x7F,"Universal: RealTime");
433 public static final int MAX_NOTE_NO = 127;
435 * General MIDI の楽器ファミリー名の配列
437 public static final String instrumentFamilyNames[] = {
458 * General MIDI の楽器名(プログラムチェンジのプログラム名)の配列
460 public static final String instrumentNames[] = {
461 "Acoustic Grand Piano",
462 "Bright Acoustic Piano",
463 "Electric Grand Piano",
485 "Acoustic Guitar (nylon)",
486 "Acoustic Guitar (steel)",
487 "Electric Guitar (jazz)",
488 "Electric Guitar (clean)",
489 "Electric Guitar (muted)",
494 "Electric Bass (finger)",
495 "Electric Bass (pick)",
548 "Lead 8 (bass + lead)",
591 * パーカッション用MIDIノート番号の最小値
593 public static final int MIN_PERCUSSION_NUMBER = 35;
595 * パーカッション用のMIDIチャンネル(通常はCH.10)における
596 * ノート番号からパーカッション名を返します。
598 * @param note_no ノート番号
601 public static String getPercussionName(int note_no) {
602 int i = note_no - MIN_PERCUSSION_NUMBER ;
603 return i>=0 && i < PERCUSSION_NAMES.length ? PERCUSSION_NAMES[i] : "(Unknown)" ;
605 public static final String PERCUSSION_NAMES[] = {
606 "Acoustic Bass Drum",
654 public static final String nsx39LyricElements[] = {
660 "さ","すぃ","す","せ","そ",
661 "ざ","ずぃ","ず","ぜ","ぞ",
662 "しゃ","し","しゅ","しぇ","しょ",
663 "じゃ","じ","じゅ","じぇ","じょ",
664 "た","てぃ","とぅ","て","と",
665 "だ","でぃ","どぅ","で","ど",
667 "ちゃ","ち","ちゅ","ちぇ","ちょ",
668 "つぁ","つぃ","つ","つぇ","つぉ",
677 "ふぁ","ふぃ","ふゅ","ふぇ","ふぉ",
687 * MIDIメッセージの内容を文字列で返します。
688 * @param msg MIDIメッセージ
689 * @param charset MIDIメタメッセージに含まれるテキストデータの文字コード
690 * @return MIDIメッセージの内容を表す文字列
692 public static String msgToString(MidiMessage msg, Charset charset) {
694 if( msg instanceof ShortMessage ) {
695 ShortMessage shortmsg = (ShortMessage)msg;
696 int status = msg.getStatus();
697 String statusName = getStatusName(status);
698 int data1 = shortmsg.getData1();
699 int data2 = shortmsg.getData2();
700 if( isChannelMessage(status) ) {
701 int channel = shortmsg.getChannel();
702 String channelPrefix = "Ch."+(channel+1) + ": ";
703 String statusPrefix = (
704 statusName == null ? String.format("status=0x%02X",status) : statusName
706 int cmd = shortmsg.getCommand();
708 case ShortMessage.NOTE_OFF:
709 case ShortMessage.NOTE_ON:
710 str += channelPrefix + statusPrefix + data1;
712 if( MIDISpec.isRhythmPart(channel) ) {
713 str += getPercussionName(data1);
716 str += NoteSymbol.noteNoToSymbol(data1);
718 str +="] Velocity=" + data2;
720 case ShortMessage.POLY_PRESSURE:
721 str += channelPrefix + statusPrefix + "Note=" + data1 + " Pressure=" + data2;
723 case ShortMessage.PROGRAM_CHANGE:
724 str += channelPrefix + statusPrefix + data1 + ":[" + instrumentNames[data1] + "]";
725 if( data2 != 0 ) str += " data2=" + data2;
727 case ShortMessage.CHANNEL_PRESSURE:
728 str += channelPrefix + statusPrefix + data1;
729 if( data2 != 0 ) str += " data2=" + data2;
731 case ShortMessage.PITCH_BEND:
733 int val = ((data1 & 0x7F) | ((data2 & 0x7F) << 7));
734 str += channelPrefix + statusPrefix + ( (val-8192) * 100 / 8191) + "% (" + val + ")";
737 case ShortMessage.CONTROL_CHANGE:
739 // Control / Mode message name
740 String ctrl_name = getControllerName(data1);
741 str += channelPrefix + (data1 < 0x78 ? "CtrlChg: " : "ModeMsg: ");
742 if( ctrl_name == null ) {
743 str += " No.=" + data1 + " Value=" + data2;
748 // Controller's value
750 case 0x40: case 0x41: case 0x42: case 0x43: case 0x45:
751 str += " " + ( data2==0x3F?"OFF":data2==0x40?"ON":data2 );
753 case 0x44: // Legato Footswitch
754 str += " " + ( data2==0x3F?"Normal":data2==0x40?"Legato":data2 );
756 case 0x7A: // Local Control
757 str += " " + ( data2==0x00?"OFF":data2==0x7F?"ON":data2 );
767 // Never reached here
771 else { // System Message
772 str += (statusName == null ? ("status="+status) : statusName );
773 str += " (" + data1 + "," + data2 + ")";
777 else if( msg instanceof MetaMessage ) {
778 MetaMessage metamsg = (MetaMessage)msg;
779 byte[] msgdata = metamsg.getData();
780 int msgtype = metamsg.getType();
782 String meta_name = getMetaName(msgtype);
783 if( meta_name == null ) {
784 str += "Unknown MessageType="+msgtype + " Values=(";
785 for( byte b : msgdata ) str += String.format( " %02X", b );
789 // Add the message type name
793 if( hasMetaMessageText(msgtype) ) {
794 str +=" ["+(new String(msgdata,charset))+"]";
797 // Add the numeric data
799 case 0x00: // Sequence Number (for MIDI Format 2)
800 if( msgdata.length == 2 ) {
801 str += String.format(
803 ((msgdata[0] & 0xFF) << 8) | (msgdata[1] & 0xFF)
807 str += ": Size not 2 byte : data=(";
808 for( byte b : msgdata ) str += String.format( " %02X", b );
811 case 0x20: // MIDI Ch.Prefix
812 case 0x21: // MIDI Output Port
813 if( msgdata.length == 1 ) {
814 str += String.format( ": %02X", msgdata[0] & 0xFF );
817 str += ": Size not 1 byte : data=(";
818 for( byte b : msgdata ) str += String.format( " %02X", b );
822 str += ": " + byteArrayToQpmTempo( msgdata ) + "[QPM] (";
823 for( byte b : msgdata ) str += String.format( " %02X", b );
826 case 0x54: // SMPTE Offset
827 if( msgdata.length == 5 ) {
829 + (msgdata[0] & 0xFF) + ":"
830 + (msgdata[1] & 0xFF) + ":"
831 + (msgdata[2] & 0xFF) + "."
832 + (msgdata[3] & 0xFF) + "."
833 + (msgdata[4] & 0xFF);
836 str += ": Size not 5 byte : data=(";
837 for( byte b : msgdata ) str += String.format( " %02X", b );
840 case 0x58: // Time Signature
841 if( msgdata.length == 4 ) {
842 str +=": " + msgdata[0] + "/" + (1 << msgdata[1]);
843 str +=", "+msgdata[2]+"[clk/beat], "+msgdata[3]+"[32nds/24clk]";
846 str += ": Size not 4 byte : data=(";
847 for( byte b : msgdata ) str += String.format( " %02X", b );
850 case 0x59: // Key Signature
851 if( msgdata.length == 2 ) {
852 Key key = new Key(msgdata);
853 str += ": " + key.signatureDescription();
854 str += " (" + key.toStringIn(SymbolLanguage.NAME) + ")";
857 str += ": Size not 2 byte : data=(";
858 for( byte b : msgdata ) str += String.format( " %02X", b );
861 case 0x7F: // Sequencer Specific Meta Event
863 for( byte b : msgdata ) str += String.format( " %02X", b );
869 else if( msg instanceof SysexMessage ) {
870 SysexMessage sysexmsg = (SysexMessage)msg;
871 int status = sysexmsg.getStatus();
872 byte[] msgdata = sysexmsg.getData();
875 case SysexMessage.SYSTEM_EXCLUSIVE:
878 case SysexMessage.SPECIAL_SYSTEM_EXCLUSIVE:
879 str += "SysEx(Special): ";
882 str += "SysEx: Invalid (status="+status+") ";
885 if( msgdata.length < 1 ) {
886 str += " Invalid data size: " + msgdata.length;
889 int manufacturerId = (int)(msgdata[0] & 0xFF);
890 int deviceId = (int)(msgdata[1] & 0xFF);
891 int modelId = (int)(msgdata[2] & 0xFF);
892 String manufacturerName = SYSEX_MANUFACTURER_NAMES.get(manufacturerId);
893 if( manufacturerName == null ) {
894 manufacturerName = String.format("[Manufacturer code %02X]", msgdata[0]);
896 str += manufacturerName + String.format(" (DevID=0x%02X)", deviceId);
897 switch( manufacturerId ) {
898 case 0x7E: // Non-Realtime Universal
900 int sub_id_1 = modelId;
901 int sub_id_2 = (int)(msgdata[3] & 0xFF);
903 case 0x09: // General MIDI (GM)
905 case 0x01: str += " GM System ON"; return str;
906 case 0x02: str += " GM System OFF"; return str;
913 // case 0x7F: // Realtime Universal
918 str += " [GS]"; dataBytePos++;
919 if( msgdata[3]==0x12 ) {
920 str += "DT1:"; dataBytePos++;
921 switch( msgdata[4] ) {
923 if( msgdata[5]==0x00 ) {
924 if( msgdata[6]==0x7F ) {
925 if( msgdata[7]==0x00 ) {
926 str += " [88] System Mode Set (Mode 1: Single Module)"; return str;
928 else if( msgdata[7]==0x01 ) {
929 str += " [88] System Mode Set (Mode 2: Double Module)"; return str;
933 else if( msgdata[5]==0x01 ) {
934 int port = (msgdata[7] & 0xFF);
935 str += String.format(
936 " [88] Ch.Msg Rx Port: Block=0x%02X, Port=%s",
938 port==0?"A":port==1?"B":String.format("0x%02X",port)
944 if( msgdata[5]==0x00 ) {
945 switch( msgdata[6] ) {
946 case 0x00: str += " Master Tune: "; dataBytePos += 3; break;
947 case 0x04: str += " Master Volume: "; dataBytePos += 3; break;
948 case 0x05: str += " Master Key Shift: "; dataBytePos += 3; break;
949 case 0x06: str += " Master Pan: "; dataBytePos += 3; break;
951 switch( msgdata[7] ) {
952 case 0x00: str += " GS Reset"; return str;
953 case 0x7F: str += " Exit GS Mode"; return str;
958 else if( msgdata[5]==0x01 ) {
959 switch( msgdata[6] ) {
960 // case 0x00: str += ""; break;
961 // case 0x10: str += ""; break;
962 case 0x30: str += " Reverb Macro: "; dataBytePos += 3; break;
963 case 0x31: str += " Reverb Character: "; dataBytePos += 3; break;
964 case 0x32: str += " Reverb Pre-LPF: "; dataBytePos += 3; break;
965 case 0x33: str += " Reverb Level: "; dataBytePos += 3; break;
966 case 0x34: str += " Reverb Time: "; dataBytePos += 3; break;
967 case 0x35: str += " Reverb Delay FB: "; dataBytePos += 3; break;
968 case 0x36: str += " Reverb Chorus Level: "; dataBytePos += 3; break;
969 case 0x37: str += " [88] Reverb Predelay Time: "; dataBytePos += 3; break;
970 case 0x38: str += " Chorus Macro: "; dataBytePos += 3; break;
971 case 0x39: str += " Chorus Pre-LPF: "; dataBytePos += 3; break;
972 case 0x3A: str += " Chorus Level: "; dataBytePos += 3; break;
973 case 0x3B: str += " Chorus FB: "; dataBytePos += 3; break;
974 case 0x3C: str += " Chorus Delay: "; dataBytePos += 3; break;
975 case 0x3D: str += " Chorus Rate: "; dataBytePos += 3; break;
976 case 0x3E: str += " Chorus Depth: "; dataBytePos += 3; break;
977 case 0x3F: str += " Chorus Send Level To Reverb: "; dataBytePos += 3; break;
978 case 0x40: str += " [88] Chorus Send Level To Delay: "; dataBytePos += 3; break;
979 case 0x50: str += " [88] Delay Macro: "; dataBytePos += 3; break;
980 case 0x51: str += " [88] Delay Pre-LPF: "; dataBytePos += 3; break;
981 case 0x52: str += " [88] Delay Time Center: "; dataBytePos += 3; break;
982 case 0x53: str += " [88] Delay Time Ratio Left: "; dataBytePos += 3; break;
983 case 0x54: str += " [88] Delay Time Ratio Right: "; dataBytePos += 3; break;
984 case 0x55: str += " [88] Delay Level Center: "; dataBytePos += 3; break;
985 case 0x56: str += " [88] Delay Level Left: "; dataBytePos += 3; break;
986 case 0x57: str += " [88] Delay Level Right: "; dataBytePos += 3; break;
987 case 0x58: str += " [88] Delay Level: "; dataBytePos += 3; break;
988 case 0x59: str += " [88] Delay FB: "; dataBytePos += 3; break;
989 case 0x5A: str += " [88] Delay Send Level To Reverb: "; dataBytePos += 3; break;
992 else if( msgdata[5]==0x02 ) {
993 switch( msgdata[6] ) {
994 case 0x00: str += " [88] EQ Low Freq: "; dataBytePos += 3; break;
995 case 0x01: str += " [88] EQ Low Gain: "; dataBytePos += 3; break;
996 case 0x02: str += " [88] EQ High Freq: "; dataBytePos += 3; break;
997 case 0x03: str += " [88] EQ High Gain: "; dataBytePos += 3; break;
1000 else if( msgdata[5]==0x03 ) {
1001 if( msgdata[6] == 0x00 ) {
1002 str += " [Pro] EFX Type: "; dataBytePos += 3;
1004 else if( msgdata[6] >= 0x03 && msgdata[6] <= 0x16 ) {
1005 str += String.format(" [Pro] EFX Param %d", msgdata[6]-2 );
1008 else if( msgdata[6] == 0x17 ) {
1009 str += " [Pro] EFX Send Level To Reverb: "; dataBytePos += 3;
1011 else if( msgdata[6] == 0x18 ) {
1012 str += " [Pro] EFX Send Level To Chorus: "; dataBytePos += 3;
1014 else if( msgdata[6] == 0x19 ) {
1015 str += " [Pro] EFX Send Level To Delay: "; dataBytePos += 3;
1017 else if( msgdata[6] == 0x1B ) {
1018 str += " [Pro] EFX Ctrl Src1: "; dataBytePos += 3;
1020 else if( msgdata[6] == 0x1C ) {
1021 str += " [Pro] EFX Ctrl Depth1: "; dataBytePos += 3;
1023 else if( msgdata[6] == 0x1D ) {
1024 str += " [Pro] EFX Ctrl Src2: "; dataBytePos += 3;
1026 else if( msgdata[6] == 0x1E ) {
1027 str += " [Pro] EFX Ctrl Depth2: "; dataBytePos += 3;
1029 else if( msgdata[6] == 0x1F ) {
1030 str += " [Pro] EFX Send EQ Switch: "; dataBytePos += 3;
1033 else if( (msgdata[5] & 0xF0) == 0x10 ) {
1034 int ch = (msgdata[5] & 0x0F);
1035 if( ch <= 9 ) ch--; else if( ch == 0 ) ch = 9;
1036 if( msgdata[6]==0x02 ) {
1037 str += String.format(
1038 " Rx Ch: Part=%d(0x%02X) Ch=0x%02X", (ch+1), msgdata[5], msgdata[7]
1042 else if( msgdata[6]==0x15 ) {
1044 switch( msgdata[7] ) {
1045 case 0: map = " NormalPart"; break;
1046 case 1: map = " DrumMap1"; break;
1047 case 2: map = " DrumMap2"; break;
1048 default: map = String.format("0x%02X",msgdata[7]); break;
1050 str += String.format(
1051 " Rhythm Part: Ch=%d(0x%02X) Map=%s",
1058 else if( (msgdata[5] & 0xF0) == 0x40 ) {
1059 int ch = (msgdata[5] & 0x0F);
1060 if( ch <= 9 ) ch--; else if( ch == 0 ) ch = 9;
1061 int dt = (msgdata[7] & 0xFF);
1062 if( msgdata[6]==0x20 ) {
1063 str += String.format(
1064 " [88] EQ: Ch=%d(0x%02X) %s",
1066 dt==0 ? "OFF" : dt==1 ? "ON" : String.format("0x%02X",dt)
1069 else if( msgdata[6]==0x22 ) {
1070 str += String.format(
1071 " [Pro] Part EFX Assign: Ch=%d(0x%02X) %s",
1073 dt==0 ? "ByPass" : dt==1 ? "EFX" : String.format("0x%02X",dt)
1082 str += " [GS-LCD]"; dataBytePos++;
1083 if( msgdata[3]==0x12 ) {
1084 str += " [DT1]"; dataBytePos++;
1085 if( msgdata[4]==0x10 && msgdata[5]==0x00 && msgdata[6]==0x00 ) {
1087 str += " Disp [" +(new String(
1088 msgdata, dataBytePos, msgdata.length - dataBytePos - 2
1093 case 0x14: str += " [D-50]"; dataBytePos++; break;
1094 case 0x16: str += " [MT-32]"; dataBytePos++; break;
1097 case 0x43: // Yamaha
1098 if( (deviceId & 0xF0) == 0x10 && modelId == 0x4C ) {
1099 str += " [XG]Dev#="+(deviceId & 0x0F);
1101 if( msgdata[3]==0 && msgdata[4]==0 && msgdata[5]==0x7E && msgdata[6]==0 ) {
1102 str += " System ON";
1107 else if( deviceId == 0x79 && modelId == 9 ) {
1108 str += " [eVocaloid]";
1110 if( msgdata[3]==0x11 && msgdata[4]==0x0A && msgdata[5]==0 ) {
1111 StringBuilder p = new StringBuilder();
1112 for( int i=6; i<msgdata.length; i++ ) {
1113 int b = (msgdata[i] & 0xFF);
1114 if( b == 0xF7 ) break;
1116 b >= MIDISpec.nsx39LyricElements.length ?
1117 "?": MIDISpec.nsx39LyricElements[b]
1121 str += " pronounce["+p+"]";
1131 for( i = dataBytePos; i<msgdata.length-1; i++ ) {
1132 str += String.format( " %02X", msgdata[i] );
1134 if( i < msgdata.length && (int)(msgdata[i] & 0xFF) != 0xF7 ) {
1135 str+=" [ Invalid EOX " + String.format( "%02X", msgdata[i] ) + " ]";
1140 byte[] msg_data = msg.getMessage();
1142 for( byte b : msg_data ) {
1143 str += String.format( " %02X", b );
1148 public static boolean isRhythmPart(int ch) { return (ch == 9); }