1 package camidion.chordhelper.music;
2 import java.nio.charset.Charset;
3 import java.util.HashMap;
6 import javax.sound.midi.InvalidMidiDataException;
7 import javax.sound.midi.MetaMessage;
8 import javax.sound.midi.MidiEvent;
9 import javax.sound.midi.MidiMessage;
10 import javax.sound.midi.Sequence;
11 import javax.sound.midi.ShortMessage;
12 import javax.sound.midi.SysexMessage;
13 import javax.sound.midi.Track;
15 * MIDI仕様(システムエクスクルーシブ含む)
17 public class MIDISpec {
18 public static final int MAX_CHANNELS = 16;
19 public static final int PITCH_BEND_NONE = 8192;
23 private static final Map<Integer,String>
24 META_MESSAGE_TYPE_NAMES = new HashMap<Integer,String>() {
26 put(0x00, "Seq Number");
28 put(0x02, "Copyright");
29 put(0x03, "Seq/Track Name");
30 put(0x04, "Instrument Name");
33 put(0x07, "Cue Point");
34 put(0x08, "Program Name");
35 put(0x09, "Device Name");
36 put(0x20, "MIDI Ch.Prefix");
37 put(0x21, "MIDI Output Port");
38 put(0x2F, "End Of Track");
40 put(0x54, "SMPTE Offset");
41 put(0x58, "Time Signature");
42 put(0x59, "Key Signature");
43 put(0x7F, "Sequencer Specific");
48 * @param metaMessageType メタメッセージタイプ
49 * @return メタメッセージタイプの名前
51 public static String getMetaName(int metaMessageType) {
52 return META_MESSAGE_TYPE_NAMES.get(metaMessageType);
55 * メタメッセージタイプがテキストのつくものか調べます。
56 * @param metaMessageType メタメッセージタイプ
57 * @return テキストがつくときtrue
59 public static boolean hasMetaText(int metaMessageType) {
60 return (metaMessageType > 0 && metaMessageType < 10);
63 * メタメッセージタイプが拍子記号か調べます。
64 * @param metaMessageType メタメッセージタイプ
67 public static boolean isTimeSignature(int metaMessageType) {
68 return metaMessageType == 0x58;
71 * MIDIメッセージが拍子記号か調べます。
72 * @param msg MIDIメッセージ
75 public static boolean isTimeSignature(MidiMessage midiMessage) {
76 if ( !(midiMessage instanceof MetaMessage) )
78 return isTimeSignature( ((MetaMessage)midiMessage).getType() );
81 * メタメッセージタイプが EOT (End Of Track) か調べます。
82 * @param metaMessageType メタメッセージタイプ
85 public static boolean isEOT(int metaMessageType) {
86 return metaMessageType == 0x2F;
89 * MIDIメッセージが EOT (End Of Track) か調べます。
90 * @param midiMessage MIDIメッセージ
93 public static boolean isEOT(MidiMessage midiMessage) {
94 if ( !(midiMessage instanceof MetaMessage) )
96 return isEOT( ((MetaMessage)midiMessage).getType() );
101 public static final int MICROSECOND_PER_MINUTE = (60 * 1000 * 1000);
103 * MIDIのテンポメッセージについているバイト列をQPM単位のテンポに変換します。
107 public static int byteArrayToQpmTempo(byte[] b) {
108 int tempoInUsPerQuarter
109 = ((b[0] & 0xFF) << 16) | ((b[1] & 0xFF) << 8) | (b[2] & 0xFF);
110 return MICROSECOND_PER_MINUTE / tempoInUsPerQuarter;
113 * QPM単位のテンポをMIDIのテンポメッセージ用バイト列に変換します。
114 * @param qpm テンポ[QPM]
115 * @return MIDIのテンポメッセージ用バイト列
117 public static byte[] qpmTempoToByteArray(int qpm) {
118 int tempoInUsPerQuarter = MICROSECOND_PER_MINUTE / qpm;
119 byte[] b = new byte[3];
120 b[0] = (byte)((tempoInUsPerQuarter >> 16) & 0xFF);
121 b[1] = (byte)((tempoInUsPerQuarter >> 8) & 0xFF);
122 b[2] = (byte)(tempoInUsPerQuarter & 0xFF);
127 * @param track MIDIトラック
130 public static byte[] getNameBytesOf(Track track) {
133 MetaMessage metaMessage;
134 for( int i=0; i<track.size(); i++ ) {
135 midiEvent = track.get(i);
136 if( midiEvent.getTick() > 0 ) { // No more event at top, try next track
139 message = midiEvent.getMessage();
140 if( ! (message instanceof MetaMessage) ) { // Not meta message
143 metaMessage = (MetaMessage)message;
144 if( metaMessage.getType() != 0x03 ) { // Not sequence name
147 return metaMessage.getData();
153 * @param track MIDIトラック
155 * @return 成功:true、失敗:false
157 public static boolean setNameBytesOf(Track track, byte[] name) {
158 MidiEvent midiEvent = null;
159 MidiMessage msg = null;
160 MetaMessage metaMsg = null;
161 for( int i=0; i<track.size(); i++ ) {
163 (midiEvent = track.get(i)).getTick() > 0
165 (msg = midiEvent.getMessage()) instanceof MetaMessage
167 (metaMsg = (MetaMessage)msg).getType() == 0x03
173 if( metaMsg == null ) {
174 if( name.length == 0 ) return false;
175 track.add(new MidiEvent(
176 (MidiMessage)(metaMsg = new MetaMessage()), 0
180 metaMsg.setMessage(0x03, name, name.length);
182 catch( InvalidMidiDataException e ) {
190 * <p>トラック名の入った最初のトラックにあるトラック名を
193 * @param seq MIDIシーケンス
194 * @return シーケンス名のバイト列
196 public static byte[] getNameBytesOf(Sequence seq) {
197 // Returns name of the MIDI sequence.
198 // A sequence name is placed at top of first track of the sequence.
200 Track tracks[] = seq.getTracks();
202 for( Track track : tracks )
203 if( (b = getNameBytesOf(track)) != null ) return b;
209 * 設定に失敗した場合、順に次のトラックへの設定を試みます。
212 * @param seq MIDIシーケンス
213 * @param name シーケンス名のバイト列
214 * @return 成功:true、失敗:false
216 public static boolean setNameBytesOf(Sequence seq, byte[] name) {
217 Track tracks[] = seq.getTracks();
218 for( Track track : tracks )
219 if( setNameBytesOf(track,name) ) return true;
222 ///////////////////////////////////////////////////////////////////
224 // Channel Message / System Message
228 * @param status MIDIステータス
231 public static String getStatusName( int status ) {
232 if( status < 0x80 ) {
236 else if ( status < 0xF0 ) {
238 return ch_msg_status_names[ (status >> 4) - 0x08 ];
240 else if ( status <= 0xFF ) {
242 return sys_msg_names[ status - 0xF0 ];
247 * 指定のMIDIショートメッセージがチャンネルメッセージかどうか調べます。
248 * @param msg MIDIメッセージ
249 * @return MIDIチャンネルメッセージの場合true
251 public static boolean isChannelMessage( ShortMessage msg ) {
252 return isChannelMessage( msg.getStatus() );
255 * MIDIステータスがチャンネルメッセージかどうか調べます。
256 * @param status MIDIステータス
257 * @return MIDIチャンネルメッセージの場合true
259 public static boolean isChannelMessage( int status ) {
260 return ( status < 0xF0 && status >= 0x80 );
262 private static final String ch_msg_status_names[] = {
263 // 0x80 - 0xE0 : Channel Voice Message
264 // 0xB0 : Channel Mode Message
266 "Polyphonic Key Pressure", "Ctrl/Mode",
267 "Program", "Ch.Pressure", "Pitch Bend"
269 private static final String sys_msg_names[] = {
270 // 0xF0 : System Exclusive
273 // 0xF1 - 0xF7 : System Common Message
274 "MIDI Time Code Quarter Frame",
275 "Song Position Pointer", "Song Select",
276 null, null, "Tune Request", "Special SysEx",
278 // 0xF8 - 0xFF : System Realtime Message
279 // 0xFF : Meta Message (SMF only, Not for wired MIDI message)
280 "Timing Clock", null, "Start", "Continue",
281 "Stop", null, "Active Sensing", "Meta / Sys.Reset",
283 ///////////////////////////////////////////////////////////////////
285 // Control Change / Channel Mode Message
288 * コントロールチェンジの名前を返します。
289 * @param controllerNumber コントローラ番号
290 * @return コントロールチェンジの名前
292 public static String getControllerName( int controllerNumber ) {
293 if( controllerNumber < 0x00 ) {
296 else if( controllerNumber < 0x20 ) {
297 String s = controllerNames0[controllerNumber];
298 if( s != null ) s += " (MSB)";
301 else if( controllerNumber < 0x40 ) {
302 String s = controllerNames0[controllerNumber - 0x20];
303 if( s != null ) s += " (LSB)";
306 else if( controllerNumber < 0x78 ) {
307 return controllerMomentarySwitchNames[controllerNumber - 0x40];
309 else if( controllerNumber < 0x80 ) {
310 return controllerModeMessageNames[controllerNumber - 0x78];
316 private static final String controllerNames0[] = {
319 "Bank Select", "Modulation Depth", "Breath Controller", null,
320 "Foot Controller", "Portamento Time", "Data Entry", "Volume",
321 "Balance", null, "Pan", "Expression",
322 "Effect Control 1", "Effect Control 2", null, null,
325 "General Purpose 1", "General Purpose 2",
326 "General Purpose 3", "General Purpose 4",
327 null, null, null, null,
328 null, null, null, null,
329 null, null, null, null,
333 private static final String controllerMomentarySwitchNames[] = {
336 "Damper Pedal (Sustain)", "Portamento",
337 "Sustenuto", "Soft Pedal",
338 "Legato Footswitch", "Hold 2",
339 "Sound Controller 1 (Sound Variation)",
340 "Sound Controller 2 (Timbre/Harmonic Intens)",
341 "Sound Controller 3 (Release Time)",
342 "Sound Controller 4 (Attack Time)",
343 "Sound Controller 5 (Brightness)",
344 "Sound Controller 6 (Decay Time)",
345 "Sound Controller 7 (Vibrato Rate)",
346 "Sound Controller 8 (Vibrato Depth)",
347 "Sound Controller 9 (Vibrato Delay)",
348 "Sound Controller 10 (Undefined)",
351 "General Purpose 5", "General Purpose 6 (Temp Change)",
352 "General Purpose 7", "General Purpose 8",
353 "Portamento Control", null, null, null,
354 null, null, null, "Reverb (Ext.Effects Depth)",
355 "Tremelo Depth", "Chorus Depth",
356 "Celeste (Detune) Depth", "Phaser Depth",
359 "Data Increment", "Data Decrement",
360 "NRPN (LSB)", "NRPN (MSB)",
361 "RPN (LSB)", "RPN (MSB)", null, null,
362 null, null, null, null,
363 null, null, null, null,
366 null, null, null, null,
367 null, null, null, null
369 private static final String controllerModeMessageNames[] = {
371 "All Sound OFF", "Reset All Controllers",
372 "Local Control", "All Notes OFF",
373 "Omni Mode OFF", "Omni Mode ON",
374 "Mono Mode ON", "Poly Mode ON"
376 ///////////////////////////////////////////////////////////////////
381 * システムエクスクルーシブの製造者IDをキーにして製造者名を返すマップ
383 public static final Map<Integer,String>
384 SYSEX_MANUFACTURER_NAMES = new HashMap<Integer,String>() {
391 put(0x7D,"Non-Commercial");
392 put(0x7E,"Universal: Non-RealTime");
393 put(0x7F,"Universal: RealTime");
399 public static final int MAX_NOTE_NO = 127;
401 * General MIDI の楽器ファミリー名の配列
403 public static final String instrumentFamilyNames[] = {
424 * General MIDI の楽器名(プログラムチェンジのプログラム名)の配列
426 public static final String instrumentNames[] = {
427 "Acoustic Grand Piano",
428 "Bright Acoustic Piano",
429 "Electric Grand Piano",
451 "Acoustic Guitar (nylon)",
452 "Acoustic Guitar (steel)",
453 "Electric Guitar (jazz)",
454 "Electric Guitar (clean)",
455 "Electric Guitar (muted)",
460 "Electric Bass (finger)",
461 "Electric Bass (pick)",
514 "Lead 8 (bass + lead)",
557 * パーカッション用MIDIノート番号の最小値
559 public static final int MIN_PERCUSSION_NUMBER = 35;
561 * パーカッション用のMIDIチャンネル(通常はCH.10)における
562 * ノート番号からパーカッション名を返します。
564 * @param note_no ノート番号
567 public static String getPercussionName(int note_no) {
568 int i = note_no - MIN_PERCUSSION_NUMBER ;
569 return i>=0 && i < PERCUSSION_NAMES.length ? PERCUSSION_NAMES[i] : "(Unknown)" ;
571 public static final String PERCUSSION_NAMES[] = {
572 "Acoustic Bass Drum",
620 public static final String nsx39LyricElements[] = {
626 "さ","すぃ","す","せ","そ",
627 "ざ","ずぃ","ず","ぜ","ぞ",
628 "しゃ","し","しゅ","しぇ","しょ",
629 "じゃ","じ","じゅ","じぇ","じょ",
630 "た","てぃ","とぅ","て","と",
631 "だ","でぃ","どぅ","で","ど",
633 "ちゃ","ち","ちゅ","ちぇ","ちょ",
634 "つぁ","つぃ","つ","つぇ","つぉ",
643 "ふぁ","ふぃ","ふゅ","ふぇ","ふぉ",
653 * MIDIメッセージの内容を文字列で返します。
654 * @param msg MIDIメッセージ
655 * @param charset MIDIメタメッセージに含まれるテキストデータの文字コード
656 * @return MIDIメッセージの内容を表す文字列
658 public static String msgToString(MidiMessage msg, Charset charset) {
660 if( msg instanceof ShortMessage ) {
661 ShortMessage shortmsg = (ShortMessage)msg;
662 int status = msg.getStatus();
663 String statusName = getStatusName(status);
664 int data1 = shortmsg.getData1();
665 int data2 = shortmsg.getData2();
666 if( isChannelMessage(status) ) {
667 int channel = shortmsg.getChannel();
668 String channelPrefix = "Ch."+(channel+1) + ": ";
669 String statusPrefix = (
670 statusName == null ? String.format("status=0x%02X",status) : statusName
672 int cmd = shortmsg.getCommand();
674 case ShortMessage.NOTE_OFF:
675 case ShortMessage.NOTE_ON:
676 str += channelPrefix + statusPrefix + data1;
678 if( MIDISpec.isRhythmPart(channel) ) {
679 str += getPercussionName(data1);
682 str += NoteSymbol.noteNoToSymbol(data1);
684 str +="] Velocity=" + data2;
686 case ShortMessage.POLY_PRESSURE:
687 str += channelPrefix + statusPrefix + "Note=" + data1 + " Pressure=" + data2;
689 case ShortMessage.PROGRAM_CHANGE:
690 str += channelPrefix + statusPrefix + data1 + ":[" + instrumentNames[data1] + "]";
691 if( data2 != 0 ) str += " data2=" + data2;
693 case ShortMessage.CHANNEL_PRESSURE:
694 str += channelPrefix + statusPrefix + data1;
695 if( data2 != 0 ) str += " data2=" + data2;
697 case ShortMessage.PITCH_BEND:
699 int val = ((data1 & 0x7F) | ((data2 & 0x7F) << 7));
700 str += channelPrefix + statusPrefix + ( (val-8192) * 100 / 8191) + "% (" + val + ")";
703 case ShortMessage.CONTROL_CHANGE:
705 // Control / Mode message name
706 String ctrl_name = getControllerName(data1);
707 str += channelPrefix + (data1 < 0x78 ? "CtrlChg: " : "ModeMsg: ");
708 if( ctrl_name == null ) {
709 str += " No.=" + data1 + " Value=" + data2;
714 // Controller's value
716 case 0x40: case 0x41: case 0x42: case 0x43: case 0x45:
717 str += " " + ( data2==0x3F?"OFF":data2==0x40?"ON":data2 );
719 case 0x44: // Legato Footswitch
720 str += " " + ( data2==0x3F?"Normal":data2==0x40?"Legato":data2 );
722 case 0x7A: // Local Control
723 str += " " + ( data2==0x00?"OFF":data2==0x7F?"ON":data2 );
733 // Never reached here
737 else { // System Message
738 str += (statusName == null ? ("status="+status) : statusName );
739 str += " (" + data1 + "," + data2 + ")";
743 else if( msg instanceof MetaMessage ) {
744 MetaMessage metamsg = (MetaMessage)msg;
745 byte[] msgdata = metamsg.getData();
746 int msgtype = metamsg.getType();
748 String meta_name = getMetaName(msgtype);
749 if( meta_name == null ) {
750 str += "Unknown MessageType="+msgtype + " Values=(";
751 for( byte b : msgdata ) str += String.format( " %02X", b );
755 // Add the message type name
759 if( hasMetaText(msgtype) ) {
760 str +=" ["+(new String(msgdata,charset))+"]";
763 // Add the numeric data
765 case 0x00: // Sequence Number (for MIDI Format 2)
766 if( msgdata.length == 2 ) {
767 str += String.format(
769 ((msgdata[0] & 0xFF) << 8) | (msgdata[1] & 0xFF)
773 str += ": Size not 2 byte : data=(";
774 for( byte b : msgdata ) str += String.format( " %02X", b );
777 case 0x20: // MIDI Ch.Prefix
778 case 0x21: // MIDI Output Port
779 if( msgdata.length == 1 ) {
780 str += String.format( ": %02X", msgdata[0] & 0xFF );
783 str += ": Size not 1 byte : data=(";
784 for( byte b : msgdata ) str += String.format( " %02X", b );
788 str += ": " + byteArrayToQpmTempo( msgdata ) + "[QPM] (";
789 for( byte b : msgdata ) str += String.format( " %02X", b );
792 case 0x54: // SMPTE Offset
793 if( msgdata.length == 5 ) {
795 + (msgdata[0] & 0xFF) + ":"
796 + (msgdata[1] & 0xFF) + ":"
797 + (msgdata[2] & 0xFF) + "."
798 + (msgdata[3] & 0xFF) + "."
799 + (msgdata[4] & 0xFF);
802 str += ": Size not 5 byte : data=(";
803 for( byte b : msgdata ) str += String.format( " %02X", b );
806 case 0x58: // Time Signature
807 if( msgdata.length == 4 ) {
808 str +=": " + msgdata[0] + "/" + (1 << msgdata[1]);
809 str +=", "+msgdata[2]+"[clk/beat], "+msgdata[3]+"[32nds/24clk]";
812 str += ": Size not 4 byte : data=(";
813 for( byte b : msgdata ) str += String.format( " %02X", b );
816 case 0x59: // Key Signature
817 if( msgdata.length == 2 ) {
818 Key key = new Key(msgdata);
819 str += ": " + key.signatureDescription();
820 str += " (" + key.toStringIn(SymbolLanguage.NAME) + ")";
823 str += ": Size not 2 byte : data=(";
824 for( byte b : msgdata ) str += String.format( " %02X", b );
827 case 0x7F: // Sequencer Specific Meta Event
829 for( byte b : msgdata ) str += String.format( " %02X", b );
835 else if( msg instanceof SysexMessage ) {
836 SysexMessage sysexmsg = (SysexMessage)msg;
837 int status = sysexmsg.getStatus();
838 byte[] msgdata = sysexmsg.getData();
841 case SysexMessage.SYSTEM_EXCLUSIVE:
844 case SysexMessage.SPECIAL_SYSTEM_EXCLUSIVE:
845 str += "SysEx(Special): ";
848 str += "SysEx: Invalid (status="+status+") ";
851 if( msgdata.length < 1 ) {
852 str += " Invalid data size: " + msgdata.length;
855 int manufacturerId = (int)(msgdata[0] & 0xFF);
856 int deviceId = (int)(msgdata[1] & 0xFF);
857 int modelId = (int)(msgdata[2] & 0xFF);
858 String manufacturerName = SYSEX_MANUFACTURER_NAMES.get(manufacturerId);
859 if( manufacturerName == null ) {
860 manufacturerName = String.format("[Manufacturer code %02X]", msgdata[0]);
862 str += manufacturerName + String.format(" (DevID=0x%02X)", deviceId);
863 switch( manufacturerId ) {
864 case 0x7E: // Non-Realtime Universal
866 int sub_id_1 = modelId;
867 int sub_id_2 = (int)(msgdata[3] & 0xFF);
869 case 0x09: // General MIDI (GM)
871 case 0x01: str += " GM System ON"; return str;
872 case 0x02: str += " GM System OFF"; return str;
879 // case 0x7F: // Realtime Universal
884 str += " [GS]"; dataBytePos++;
885 if( msgdata[3]==0x12 ) {
886 str += "DT1:"; dataBytePos++;
887 switch( msgdata[4] ) {
889 if( msgdata[5]==0x00 ) {
890 if( msgdata[6]==0x7F ) {
891 if( msgdata[7]==0x00 ) {
892 str += " [88] System Mode Set (Mode 1: Single Module)"; return str;
894 else if( msgdata[7]==0x01 ) {
895 str += " [88] System Mode Set (Mode 2: Double Module)"; return str;
899 else if( msgdata[5]==0x01 ) {
900 int port = (msgdata[7] & 0xFF);
901 str += String.format(
902 " [88] Ch.Msg Rx Port: Block=0x%02X, Port=%s",
904 port==0?"A":port==1?"B":String.format("0x%02X",port)
910 if( msgdata[5]==0x00 ) {
911 switch( msgdata[6] ) {
912 case 0x00: str += " Master Tune: "; dataBytePos += 3; break;
913 case 0x04: str += " Master Volume: "; dataBytePos += 3; break;
914 case 0x05: str += " Master Key Shift: "; dataBytePos += 3; break;
915 case 0x06: str += " Master Pan: "; dataBytePos += 3; break;
917 switch( msgdata[7] ) {
918 case 0x00: str += " GS Reset"; return str;
919 case 0x7F: str += " Exit GS Mode"; return str;
924 else if( msgdata[5]==0x01 ) {
925 switch( msgdata[6] ) {
926 // case 0x00: str += ""; break;
927 // case 0x10: str += ""; break;
928 case 0x30: str += " Reverb Macro: "; dataBytePos += 3; break;
929 case 0x31: str += " Reverb Character: "; dataBytePos += 3; break;
930 case 0x32: str += " Reverb Pre-LPF: "; dataBytePos += 3; break;
931 case 0x33: str += " Reverb Level: "; dataBytePos += 3; break;
932 case 0x34: str += " Reverb Time: "; dataBytePos += 3; break;
933 case 0x35: str += " Reverb Delay FB: "; dataBytePos += 3; break;
934 case 0x36: str += " Reverb Chorus Level: "; dataBytePos += 3; break;
935 case 0x37: str += " [88] Reverb Predelay Time: "; dataBytePos += 3; break;
936 case 0x38: str += " Chorus Macro: "; dataBytePos += 3; break;
937 case 0x39: str += " Chorus Pre-LPF: "; dataBytePos += 3; break;
938 case 0x3A: str += " Chorus Level: "; dataBytePos += 3; break;
939 case 0x3B: str += " Chorus FB: "; dataBytePos += 3; break;
940 case 0x3C: str += " Chorus Delay: "; dataBytePos += 3; break;
941 case 0x3D: str += " Chorus Rate: "; dataBytePos += 3; break;
942 case 0x3E: str += " Chorus Depth: "; dataBytePos += 3; break;
943 case 0x3F: str += " Chorus Send Level To Reverb: "; dataBytePos += 3; break;
944 case 0x40: str += " [88] Chorus Send Level To Delay: "; dataBytePos += 3; break;
945 case 0x50: str += " [88] Delay Macro: "; dataBytePos += 3; break;
946 case 0x51: str += " [88] Delay Pre-LPF: "; dataBytePos += 3; break;
947 case 0x52: str += " [88] Delay Time Center: "; dataBytePos += 3; break;
948 case 0x53: str += " [88] Delay Time Ratio Left: "; dataBytePos += 3; break;
949 case 0x54: str += " [88] Delay Time Ratio Right: "; dataBytePos += 3; break;
950 case 0x55: str += " [88] Delay Level Center: "; dataBytePos += 3; break;
951 case 0x56: str += " [88] Delay Level Left: "; dataBytePos += 3; break;
952 case 0x57: str += " [88] Delay Level Right: "; dataBytePos += 3; break;
953 case 0x58: str += " [88] Delay Level: "; dataBytePos += 3; break;
954 case 0x59: str += " [88] Delay FB: "; dataBytePos += 3; break;
955 case 0x5A: str += " [88] Delay Send Level To Reverb: "; dataBytePos += 3; break;
958 else if( msgdata[5]==0x02 ) {
959 switch( msgdata[6] ) {
960 case 0x00: str += " [88] EQ Low Freq: "; dataBytePos += 3; break;
961 case 0x01: str += " [88] EQ Low Gain: "; dataBytePos += 3; break;
962 case 0x02: str += " [88] EQ High Freq: "; dataBytePos += 3; break;
963 case 0x03: str += " [88] EQ High Gain: "; dataBytePos += 3; break;
966 else if( msgdata[5]==0x03 ) {
967 if( msgdata[6] == 0x00 ) {
968 str += " [Pro] EFX Type: "; dataBytePos += 3;
970 else if( msgdata[6] >= 0x03 && msgdata[6] <= 0x16 ) {
971 str += String.format(" [Pro] EFX Param %d", msgdata[6]-2 );
974 else if( msgdata[6] == 0x17 ) {
975 str += " [Pro] EFX Send Level To Reverb: "; dataBytePos += 3;
977 else if( msgdata[6] == 0x18 ) {
978 str += " [Pro] EFX Send Level To Chorus: "; dataBytePos += 3;
980 else if( msgdata[6] == 0x19 ) {
981 str += " [Pro] EFX Send Level To Delay: "; dataBytePos += 3;
983 else if( msgdata[6] == 0x1B ) {
984 str += " [Pro] EFX Ctrl Src1: "; dataBytePos += 3;
986 else if( msgdata[6] == 0x1C ) {
987 str += " [Pro] EFX Ctrl Depth1: "; dataBytePos += 3;
989 else if( msgdata[6] == 0x1D ) {
990 str += " [Pro] EFX Ctrl Src2: "; dataBytePos += 3;
992 else if( msgdata[6] == 0x1E ) {
993 str += " [Pro] EFX Ctrl Depth2: "; dataBytePos += 3;
995 else if( msgdata[6] == 0x1F ) {
996 str += " [Pro] EFX Send EQ Switch: "; dataBytePos += 3;
999 else if( (msgdata[5] & 0xF0) == 0x10 ) {
1000 int ch = (msgdata[5] & 0x0F);
1001 if( ch <= 9 ) ch--; else if( ch == 0 ) ch = 9;
1002 if( msgdata[6]==0x02 ) {
1003 str += String.format(
1004 " Rx Ch: Part=%d(0x%02X) Ch=0x%02X", (ch+1), msgdata[5], msgdata[7]
1008 else if( msgdata[6]==0x15 ) {
1010 switch( msgdata[7] ) {
1011 case 0: map = " NormalPart"; break;
1012 case 1: map = " DrumMap1"; break;
1013 case 2: map = " DrumMap2"; break;
1014 default: map = String.format("0x%02X",msgdata[7]); break;
1016 str += String.format(
1017 " Rhythm Part: Ch=%d(0x%02X) Map=%s",
1024 else if( (msgdata[5] & 0xF0) == 0x40 ) {
1025 int ch = (msgdata[5] & 0x0F);
1026 if( ch <= 9 ) ch--; else if( ch == 0 ) ch = 9;
1027 int dt = (msgdata[7] & 0xFF);
1028 if( msgdata[6]==0x20 ) {
1029 str += String.format(
1030 " [88] EQ: Ch=%d(0x%02X) %s",
1032 dt==0 ? "OFF" : dt==1 ? "ON" : String.format("0x%02X",dt)
1035 else if( msgdata[6]==0x22 ) {
1036 str += String.format(
1037 " [Pro] Part EFX Assign: Ch=%d(0x%02X) %s",
1039 dt==0 ? "ByPass" : dt==1 ? "EFX" : String.format("0x%02X",dt)
1048 str += " [GS-LCD]"; dataBytePos++;
1049 if( msgdata[3]==0x12 ) {
1050 str += " [DT1]"; dataBytePos++;
1051 if( msgdata[4]==0x10 && msgdata[5]==0x00 && msgdata[6]==0x00 ) {
1053 str += " Disp [" +(new String(
1054 msgdata, dataBytePos, msgdata.length - dataBytePos - 2
1059 case 0x14: str += " [D-50]"; dataBytePos++; break;
1060 case 0x16: str += " [MT-32]"; dataBytePos++; break;
1063 case 0x43: // Yamaha
1064 if( (deviceId & 0xF0) == 0x10 && modelId == 0x4C ) {
1065 str += " [XG]Dev#="+(deviceId & 0x0F);
1067 if( msgdata[3]==0 && msgdata[4]==0 && msgdata[5]==0x7E && msgdata[6]==0 ) {
1068 str += " System ON";
1073 else if( deviceId == 0x79 && modelId == 9 ) {
1074 str += " [eVocaloid]";
1076 if( msgdata[3]==0x11 && msgdata[4]==0x0A && msgdata[5]==0 ) {
1077 StringBuilder p = new StringBuilder();
1078 for( int i=6; i<msgdata.length; i++ ) {
1079 int b = (msgdata[i] & 0xFF);
1080 if( b == 0xF7 ) break;
1082 b < 0 || b >= MIDISpec.nsx39LyricElements.length ?
1083 "?": MIDISpec.nsx39LyricElements[b]
1087 str += " pronounce["+p+"]";
1097 for( i = dataBytePos; i<msgdata.length-1; i++ ) {
1098 str += String.format( " %02X", msgdata[i] );
1100 if( i < msgdata.length && (int)(msgdata[i] & 0xFF) != 0xF7 ) {
1101 str+=" [ Invalid EOX " + String.format( "%02X", msgdata[i] ) + " ]";
1106 byte[] msg_data = msg.getMessage();
1108 for( byte b : msg_data ) {
1109 str += String.format( " %02X", b );
1114 public static boolean isRhythmPart(int ch) { return (ch == 9); }