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