1 package camidion.chordhelper.music;
2 import java.io.UnsupportedEncodingException;
3 import java.nio.charset.Charset;
4 import java.util.Arrays;
5 import java.util.HashMap;
7 import java.util.Objects;
9 import javax.sound.midi.InvalidMidiDataException;
10 import javax.sound.midi.MetaMessage;
11 import javax.sound.midi.MidiEvent;
12 import javax.sound.midi.MidiMessage;
13 import javax.sound.midi.Sequence;
14 import javax.sound.midi.ShortMessage;
15 import javax.sound.midi.SysexMessage;
16 import javax.sound.midi.Track;
18 * MIDI仕様(システムエクスクルーシブ含む)
20 public class MIDISpec {
21 public static final int MAX_CHANNELS = 16;
22 public static final int PITCH_BEND_NONE = 8192;
24 * 指定されたMIDIノート番号の音の周波数(A=440Hzチューニング時)を返します。
25 * @param noteNumber MIDIノート番号
28 public static double noteNumberToFrequency(int noteNumber) {
29 return 55 * Math.pow( 2, (double)(noteNumber - 33)/12 );
34 private static final Map<Integer,String>
35 META_MESSAGE_TYPE_NAMES = new HashMap<Integer,String>() {
37 put(0x00, "Seq Number");
39 put(0x02, "Copyright");
40 put(0x03, "Seq/Track Name");
41 put(0x04, "Instrument Name");
44 put(0x07, "Cue Point");
45 put(0x08, "Program Name");
46 put(0x09, "Device Name");
47 put(0x20, "MIDI Ch.Prefix");
48 put(0x21, "MIDI Output Port");
49 put(0x2F, "End Of Track");
51 put(0x54, "SMPTE Offset");
52 put(0x58, "Time Signature");
53 put(0x59, "Key Signature");
54 put(0x7F, "Sequencer Specific");
59 * @param metaMessageType メタメッセージタイプ
60 * @return メタメッセージタイプの名前
62 public static String getMetaName(int metaMessageType) {
63 return META_MESSAGE_TYPE_NAMES.get(metaMessageType);
66 * メタメッセージタイプがテキストのつくものか調べます。
67 * @param metaMessageType メタメッセージタイプ
68 * @return テキストがつくときtrue
70 public static boolean hasMetaMessageText(int metaMessageType) {
71 return (metaMessageType > 0 && metaMessageType < 10);
74 * メタメッセージタイプが拍子記号か調べます。
75 * @param metaMessageType メタメッセージタイプ
78 public static boolean isTimeSignature(int metaMessageType) {
79 return metaMessageType == 0x58;
82 * MIDIメッセージが拍子記号か調べます。
83 * @param msg MIDIメッセージ
86 public static boolean isTimeSignature(MidiMessage midiMessage) {
87 return (midiMessage instanceof MetaMessage) && 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 return (midiMessage instanceof MetaMessage) && isEOT(((MetaMessage)midiMessage).getType());
106 public static final int MICROSECOND_PER_MINUTE = (60 * 1000 * 1000);
108 * MIDIのテンポメッセージについているバイト列をQPM単位のテンポに変換します。
112 public static int byteArrayToQpmTempo(byte[] b) {
113 int tempoInUsPerQuarter = ((b[0] & 0xFF) << 16) | ((b[1] & 0xFF) << 8) | (b[2] & 0xFF);
114 return MICROSECOND_PER_MINUTE / tempoInUsPerQuarter;
117 * QPM単位のテンポをMIDIのテンポメッセージ用バイト列に変換します。
118 * @param qpm テンポ[QPM]
119 * @return MIDIのテンポメッセージ用バイト列
121 public static byte[] qpmTempoToByteArray(int qpm) {
122 int tempoInUsPerQuarter = MICROSECOND_PER_MINUTE / qpm;
123 byte[] b = new byte[3];
124 b[0] = (byte)((tempoInUsPerQuarter >> 16) & 0xFF);
125 b[1] = (byte)((tempoInUsPerQuarter >> 8) & 0xFF);
126 b[2] = (byte)(tempoInUsPerQuarter & 0xFF);
130 * トラック名のメタメッセージを返します。
131 * @param track 対象MIDIトラック
132 * @return トラック名のメタメッセージ(見つからない場合はnull)
134 public static MetaMessage getNameMessageOf(Track track) {
136 for( int i=0; i<track.size(); i++ ) {
137 midiEvent = track.get(i);
138 if( midiEvent.getTick() > 0 ) { // No more event at top, try next track
141 MidiMessage message = midiEvent.getMessage();
142 if( ! (message instanceof MetaMessage) ) continue;
143 MetaMessage metaMessage = (MetaMessage)message;
144 if( metaMessage.getType() == 0x03 ) return metaMessage;
150 * @param track MIDIトラック
151 * @return トラック名のバイト列(トラック名が見つからない場合はnull)
153 public static byte[] getNameBytesOf(Track track) {
154 MetaMessage metaMessage = getNameMessageOf(track);
155 return metaMessage != null ? metaMessage.getData() : null;
159 * @param track MIDIトラック
161 * @return 成功:true、失敗:false
163 public static boolean setNameBytesOf(Track track, byte[] name) {
164 MetaMessage metaMsg = getNameMessageOf(track);
165 if( metaMsg == null ) {
166 track.add(new MidiEvent((MidiMessage)(metaMsg = new MetaMessage()), 0));
169 metaMsg.setMessage(0x03, name, name.length);
171 catch( InvalidMidiDataException e ) {
179 * <p>トラック名の入った最初のトラックにあるトラック名をシーケンス名として返します。</p>
180 * @param sequence MIDIシーケンス
181 * @return シーケンス名のバイト列(見つからない場合はnull)
183 public static byte[] getNameBytesOf(Sequence sequence) {
184 return Arrays.stream(sequence.getTracks()).map(t->getNameBytesOf(t))
185 .filter(Objects::nonNull).findFirst().orElse(null);
189 * <p>先頭のトラックに設定されます。設定に失敗した場合、順に次のトラックへの設定を試みます。
192 * @param sequence MIDIシーケンス
193 * @param name シーケンス名のバイト列
194 * @return 成功:true、失敗:false
196 public static boolean setNameBytesOf(Sequence sequence, byte[] name) {
197 return Arrays.stream(sequence.getTracks()).anyMatch(t->setNameBytesOf(t,name));
200 * シーケンスの名前や歌詞など、メタイベントのテキストをもとに文字コードを判定します。
201 * 判定できなかった場合はnullを返します。
202 * @param sequence MIDIシーケンス
203 * @return 文字コード判定結果(またはnull)
205 public static Charset getCharsetOf(Sequence sequence) {
206 byte[] b = new byte[0];
207 for( Track track : sequence.getTracks() ) {
209 MetaMessage metaMessage;
210 for( int i=0; i<track.size(); i++ ) {
211 message = track.get(i).getMessage();
212 if( ! (message instanceof MetaMessage) ) continue;
213 metaMessage = (MetaMessage)message;
214 if( ! hasMetaMessageText(metaMessage.getType()) ) continue;
215 byte[] additional = metaMessage.getData();
216 byte[] concated = new byte[b.length + additional.length];
217 System.arraycopy(b, 0, concated, 0, b.length);
218 System.arraycopy(additional, 0, concated, b.length, additional.length);
222 if( b.length <= 0 ) return null;
223 final byte[] fixedBytes = b;
225 String autoDetectedName = new String(fixedBytes, "JISAutoDetect");
226 return Charset.availableCharsets().values().stream()
227 .filter(charset -> autoDetectedName.equals(new String(fixedBytes, charset)))
228 .findFirst().orElse(null);
229 } catch (UnsupportedEncodingException e) {
234 ///////////////////////////////////////////////////////////////////
236 // Channel Message / System Message
240 * @param status MIDIステータス
243 public static String getStatusName( int status ) {
244 if( status < 0x80 ) {
248 else if ( status < 0xF0 ) {
250 return ch_msg_status_names[ (status >> 4) - 0x08 ];
252 else if ( status <= 0xFF ) {
254 return sys_msg_names[ status - 0xF0 ];
259 * 指定のMIDIショートメッセージがチャンネルメッセージかどうか調べます。
260 * @param msg MIDIメッセージ
261 * @return MIDIチャンネルメッセージの場合true
263 public static boolean isChannelMessage( ShortMessage msg ) {
264 return isChannelMessage( msg.getStatus() );
267 * MIDIステータスがチャンネルメッセージかどうか調べます。
268 * @param status MIDIステータス
269 * @return MIDIチャンネルメッセージの場合true
271 public static boolean isChannelMessage( int status ) {
272 return ( status < 0xF0 && status >= 0x80 );
274 private static final String ch_msg_status_names[] = {
275 // 0x80 - 0xE0 : Channel Voice Message
276 // 0xB0 : Channel Mode Message
278 "Polyphonic Key Pressure", "Ctrl/Mode",
279 "Program", "Ch.Pressure", "Pitch Bend"
281 private static final String sys_msg_names[] = {
282 // 0xF0 : System Exclusive
285 // 0xF1 - 0xF7 : System Common Message
286 "MIDI Time Code Quarter Frame",
287 "Song Position Pointer", "Song Select",
288 null, null, "Tune Request", "Special SysEx",
290 // 0xF8 - 0xFF : System Realtime Message
291 // 0xFF : Meta Message (SMF only, Not for wired MIDI message)
292 "Timing Clock", null, "Start", "Continue",
293 "Stop", null, "Active Sensing", "Meta / Sys.Reset",
295 ///////////////////////////////////////////////////////////////////
297 // Control Change / Channel Mode Message
300 * コントロールチェンジの名前を返します。
301 * @param controllerNumber コントローラ番号
302 * @return コントロールチェンジの名前
304 public static String getControllerName( int controllerNumber ) {
305 if( controllerNumber < 0x00 ) {
308 else if( controllerNumber < 0x20 ) {
309 String s = controllerNames0[controllerNumber];
310 if( s != null ) s += " (MSB)";
313 else if( controllerNumber < 0x40 ) {
314 String s = controllerNames0[controllerNumber - 0x20];
315 if( s != null ) s += " (LSB)";
318 else if( controllerNumber < 0x78 ) {
319 return controllerMomentarySwitchNames[controllerNumber - 0x40];
321 else if( controllerNumber < 0x80 ) {
322 return controllerModeMessageNames[controllerNumber - 0x78];
328 private static final String controllerNames0[] = {
331 "Bank Select", "Modulation Depth", "Breath Controller", null,
332 "Foot Controller", "Portamento Time", "Data Entry", "Volume",
333 "Balance", null, "Pan", "Expression",
334 "Effect Control 1", "Effect Control 2", null, null,
337 "General Purpose 1", "General Purpose 2",
338 "General Purpose 3", "General Purpose 4",
339 null, null, null, null,
340 null, null, null, null,
341 null, null, null, null,
345 private static final String controllerMomentarySwitchNames[] = {
348 "Damper Pedal (Sustain)", "Portamento",
349 "Sustenuto", "Soft Pedal",
350 "Legato Footswitch", "Hold 2",
351 "Sound Controller 1 (Sound Variation)",
352 "Sound Controller 2 (Timbre/Harmonic Intens)",
353 "Sound Controller 3 (Release Time)",
354 "Sound Controller 4 (Attack Time)",
355 "Sound Controller 5 (Brightness)",
356 "Sound Controller 6 (Decay Time)",
357 "Sound Controller 7 (Vibrato Rate)",
358 "Sound Controller 8 (Vibrato Depth)",
359 "Sound Controller 9 (Vibrato Delay)",
360 "Sound Controller 10 (Undefined)",
363 "General Purpose 5", "General Purpose 6 (Temp Change)",
364 "General Purpose 7", "General Purpose 8",
365 "Portamento Control", null, null, null,
366 null, null, null, "Reverb (Ext.Effects Depth)",
367 "Tremelo Depth", "Chorus Depth",
368 "Celeste (Detune) Depth", "Phaser Depth",
371 "Data Increment", "Data Decrement",
372 "NRPN (LSB)", "NRPN (MSB)",
373 "RPN (LSB)", "RPN (MSB)", null, null,
374 null, null, null, null,
375 null, null, null, null,
378 null, null, null, null,
379 null, null, null, null
381 private static final String controllerModeMessageNames[] = {
383 "All Sound OFF", "Reset All Controllers",
384 "Local Control", "All Notes OFF",
385 "Omni Mode OFF", "Omni Mode ON",
386 "Mono Mode ON", "Poly Mode ON"
388 ///////////////////////////////////////////////////////////////////
393 * システムエクスクルーシブの製造者IDをキーにして製造者名を返すマップ
395 public static final Map<Integer,String>
396 SYSEX_MANUFACTURER_NAMES = new HashMap<Integer,String>() {
403 put(0x7D,"Non-Commercial");
404 put(0x7E,"Universal: Non-RealTime");
405 put(0x7F,"Universal: RealTime");
411 public static final int MAX_NOTE_NO = 127;
413 * General MIDI の楽器ファミリー名の配列
415 public static final String instrumentFamilyNames[] = {
436 * General MIDI の楽器名(プログラムチェンジのプログラム名)の配列
438 public static final String instrumentNames[] = {
439 "Acoustic Grand Piano",
440 "Bright Acoustic Piano",
441 "Electric Grand Piano",
463 "Acoustic Guitar (nylon)",
464 "Acoustic Guitar (steel)",
465 "Electric Guitar (jazz)",
466 "Electric Guitar (clean)",
467 "Electric Guitar (muted)",
472 "Electric Bass (finger)",
473 "Electric Bass (pick)",
526 "Lead 8 (bass + lead)",
569 * パーカッション用MIDIノート番号の最小値
571 public static final int MIN_PERCUSSION_NUMBER = 35;
573 * パーカッション用のMIDIチャンネル(通常はCH.10)における
574 * ノート番号からパーカッション名を返します。
576 * @param note_no ノート番号
579 public static String getPercussionName(int note_no) {
580 int i = note_no - MIN_PERCUSSION_NUMBER ;
581 return i>=0 && i < PERCUSSION_NAMES.length ? PERCUSSION_NAMES[i] : "(Unknown)" ;
583 public static final String PERCUSSION_NAMES[] = {
584 "Acoustic Bass Drum",
632 public static final String nsx39LyricElements[] = {
638 "さ","すぃ","す","せ","そ",
639 "ざ","ずぃ","ず","ぜ","ぞ",
640 "しゃ","し","しゅ","しぇ","しょ",
641 "じゃ","じ","じゅ","じぇ","じょ",
642 "た","てぃ","とぅ","て","と",
643 "だ","でぃ","どぅ","で","ど",
645 "ちゃ","ち","ちゅ","ちぇ","ちょ",
646 "つぁ","つぃ","つ","つぇ","つぉ",
655 "ふぁ","ふぃ","ふゅ","ふぇ","ふぉ",
665 * MIDIメッセージの内容を文字列で返します。
666 * @param msg MIDIメッセージ
667 * @param charset MIDIメタメッセージに含まれるテキストデータの文字コード
668 * @return MIDIメッセージの内容を表す文字列
670 public static String msgToString(MidiMessage msg, Charset charset) {
672 if( msg instanceof ShortMessage ) {
673 ShortMessage shortmsg = (ShortMessage)msg;
674 int status = msg.getStatus();
675 String statusName = getStatusName(status);
676 int data1 = shortmsg.getData1();
677 int data2 = shortmsg.getData2();
678 if( isChannelMessage(status) ) {
679 int channel = shortmsg.getChannel();
680 String channelPrefix = "Ch."+(channel+1) + ": ";
681 String statusPrefix = (
682 statusName == null ? String.format("status=0x%02X",status) : statusName
684 int cmd = shortmsg.getCommand();
686 case ShortMessage.NOTE_OFF:
687 case ShortMessage.NOTE_ON:
688 str += channelPrefix + statusPrefix + data1;
690 if( MIDISpec.isRhythmPart(channel) ) {
691 str += getPercussionName(data1);
694 str += Note.noteNumberToSymbol(data1);
696 str +="] Velocity=" + data2;
698 case ShortMessage.POLY_PRESSURE:
699 str += channelPrefix + statusPrefix + "Note=" + data1 + " Pressure=" + data2;
701 case ShortMessage.PROGRAM_CHANGE:
702 str += channelPrefix + statusPrefix + data1 + ":[" + instrumentNames[data1] + "]";
703 if( data2 != 0 ) str += " data2=" + data2;
705 case ShortMessage.CHANNEL_PRESSURE:
706 str += channelPrefix + statusPrefix + data1;
707 if( data2 != 0 ) str += " data2=" + data2;
709 case ShortMessage.PITCH_BEND:
711 int val = ((data1 & 0x7F) | ((data2 & 0x7F) << 7));
712 str += channelPrefix + statusPrefix + ( (val-8192) * 100 / 8191) + "% (" + val + ")";
715 case ShortMessage.CONTROL_CHANGE:
717 // Control / Mode message name
718 String ctrl_name = getControllerName(data1);
719 str += channelPrefix + (data1 < 0x78 ? "CtrlChg: " : "ModeMsg: ");
720 if( ctrl_name == null ) {
721 str += " No.=" + data1 + " Value=" + data2;
726 // Controller's value
728 case 0x40: case 0x41: case 0x42: case 0x43: case 0x45:
729 str += " " + ( data2==0x3F?"OFF":data2==0x40?"ON":data2 );
731 case 0x44: // Legato Footswitch
732 str += " " + ( data2==0x3F?"Normal":data2==0x40?"Legato":data2 );
734 case 0x7A: // Local Control
735 str += " " + ( data2==0x00?"OFF":data2==0x7F?"ON":data2 );
745 // Never reached here
749 else { // System Message
750 str += (statusName == null ? ("status="+status) : statusName );
751 str += " (" + data1 + "," + data2 + ")";
755 else if( msg instanceof MetaMessage ) {
756 MetaMessage metamsg = (MetaMessage)msg;
757 byte[] msgdata = metamsg.getData();
758 int msgtype = metamsg.getType();
760 String meta_name = getMetaName(msgtype);
761 if( meta_name == null ) {
762 str += "Unknown MessageType="+msgtype + " Values=(";
763 for( byte b : msgdata ) str += String.format( " %02X", b );
767 // Add the message type name
771 if( hasMetaMessageText(msgtype) ) {
772 str +=" ["+(new String(msgdata,charset))+"]";
775 // Add the numeric data
777 case 0x00: // Sequence Number (for MIDI Format 2)
778 if( msgdata.length == 2 ) {
779 str += String.format(
781 ((msgdata[0] & 0xFF) << 8) | (msgdata[1] & 0xFF)
785 str += ": Size not 2 byte : data=(";
786 for( byte b : msgdata ) str += String.format( " %02X", b );
789 case 0x20: // MIDI Ch.Prefix
790 case 0x21: // MIDI Output Port
791 if( msgdata.length == 1 ) {
792 str += String.format( ": %02X", msgdata[0] & 0xFF );
795 str += ": Size not 1 byte : data=(";
796 for( byte b : msgdata ) str += String.format( " %02X", b );
800 str += ": " + byteArrayToQpmTempo( msgdata ) + "[QPM] (";
801 for( byte b : msgdata ) str += String.format( " %02X", b );
804 case 0x54: // SMPTE Offset
805 if( msgdata.length == 5 ) {
807 + (msgdata[0] & 0xFF) + ":"
808 + (msgdata[1] & 0xFF) + ":"
809 + (msgdata[2] & 0xFF) + "."
810 + (msgdata[3] & 0xFF) + "."
811 + (msgdata[4] & 0xFF);
814 str += ": Size not 5 byte : data=(";
815 for( byte b : msgdata ) str += String.format( " %02X", b );
818 case 0x58: // Time Signature
819 if( msgdata.length == 4 ) {
820 str +=": " + msgdata[0] + "/" + (1 << msgdata[1]);
821 str +=", "+msgdata[2]+"[clk/beat], "+msgdata[3]+"[32nds/24clk]";
824 str += ": Size not 4 byte : data=(";
825 for( byte b : msgdata ) str += String.format( " %02X", b );
828 case 0x59: // Key Signature
829 if( msgdata.length == 2 ) {
830 Key key = new Key(msgdata);
831 str += ": " + key.signatureDescription();
832 str += " (" + key.toStringIn(Note.Language.NAME) + ")";
835 str += ": Size not 2 byte : data=(";
836 for( byte b : msgdata ) str += String.format( " %02X", b );
839 case 0x7F: // Sequencer Specific Meta Event
841 for( byte b : msgdata ) str += String.format( " %02X", b );
847 else if( msg instanceof SysexMessage ) {
848 SysexMessage sysexmsg = (SysexMessage)msg;
849 int status = sysexmsg.getStatus();
850 byte[] msgdata = sysexmsg.getData();
853 case SysexMessage.SYSTEM_EXCLUSIVE:
856 case SysexMessage.SPECIAL_SYSTEM_EXCLUSIVE:
857 str += "SysEx(Special): ";
860 str += "SysEx: Invalid (status="+status+") ";
863 if( msgdata.length < 1 ) {
864 str += " Invalid data size: " + msgdata.length;
867 int manufacturerId = (int)(msgdata[0] & 0xFF);
868 int deviceId = (int)(msgdata[1] & 0xFF);
869 int modelId = (int)(msgdata[2] & 0xFF);
870 String manufacturerName = SYSEX_MANUFACTURER_NAMES.get(manufacturerId);
871 if( manufacturerName == null ) {
872 manufacturerName = String.format("[Manufacturer code %02X]", msgdata[0]);
874 str += manufacturerName + String.format(" (DevID=0x%02X)", deviceId);
875 switch( manufacturerId ) {
876 case 0x7E: // Non-Realtime Universal
878 int sub_id_1 = modelId;
879 int sub_id_2 = (int)(msgdata[3] & 0xFF);
881 case 0x09: // General MIDI (GM)
883 case 0x01: str += " GM System ON"; return str;
884 case 0x02: str += " GM System OFF"; return str;
891 // case 0x7F: // Realtime Universal
896 str += " [GS]"; dataBytePos++;
897 if( msgdata[3]==0x12 ) {
898 str += "DT1:"; dataBytePos++;
899 switch( msgdata[4] ) {
901 if( msgdata[5]==0x00 ) {
902 if( msgdata[6]==0x7F ) {
903 if( msgdata[7]==0x00 ) {
904 str += " [88] System Mode Set (Mode 1: Single Module)"; return str;
906 else if( msgdata[7]==0x01 ) {
907 str += " [88] System Mode Set (Mode 2: Double Module)"; return str;
911 else if( msgdata[5]==0x01 ) {
912 int port = (msgdata[7] & 0xFF);
913 str += String.format(
914 " [88] Ch.Msg Rx Port: Block=0x%02X, Port=%s",
916 port==0?"A":port==1?"B":String.format("0x%02X",port)
922 if( msgdata[5]==0x00 ) {
923 switch( msgdata[6] ) {
924 case 0x00: str += " Master Tune: "; dataBytePos += 3; break;
925 case 0x04: str += " Master Volume: "; dataBytePos += 3; break;
926 case 0x05: str += " Master Key Shift: "; dataBytePos += 3; break;
927 case 0x06: str += " Master Pan: "; dataBytePos += 3; break;
929 switch( msgdata[7] ) {
930 case 0x00: str += " GS Reset"; return str;
931 case 0x7F: str += " Exit GS Mode"; return str;
936 else if( msgdata[5]==0x01 ) {
937 switch( msgdata[6] ) {
938 // case 0x00: str += ""; break;
939 // case 0x10: str += ""; break;
940 case 0x30: str += " Reverb Macro: "; dataBytePos += 3; break;
941 case 0x31: str += " Reverb Character: "; dataBytePos += 3; break;
942 case 0x32: str += " Reverb Pre-LPF: "; dataBytePos += 3; break;
943 case 0x33: str += " Reverb Level: "; dataBytePos += 3; break;
944 case 0x34: str += " Reverb Time: "; dataBytePos += 3; break;
945 case 0x35: str += " Reverb Delay FB: "; dataBytePos += 3; break;
946 case 0x36: str += " Reverb Chorus Level: "; dataBytePos += 3; break;
947 case 0x37: str += " [88] Reverb Predelay Time: "; dataBytePos += 3; break;
948 case 0x38: str += " Chorus Macro: "; dataBytePos += 3; break;
949 case 0x39: str += " Chorus Pre-LPF: "; dataBytePos += 3; break;
950 case 0x3A: str += " Chorus Level: "; dataBytePos += 3; break;
951 case 0x3B: str += " Chorus FB: "; dataBytePos += 3; break;
952 case 0x3C: str += " Chorus Delay: "; dataBytePos += 3; break;
953 case 0x3D: str += " Chorus Rate: "; dataBytePos += 3; break;
954 case 0x3E: str += " Chorus Depth: "; dataBytePos += 3; break;
955 case 0x3F: str += " Chorus Send Level To Reverb: "; dataBytePos += 3; break;
956 case 0x40: str += " [88] Chorus Send Level To Delay: "; dataBytePos += 3; break;
957 case 0x50: str += " [88] Delay Macro: "; dataBytePos += 3; break;
958 case 0x51: str += " [88] Delay Pre-LPF: "; dataBytePos += 3; break;
959 case 0x52: str += " [88] Delay Time Center: "; dataBytePos += 3; break;
960 case 0x53: str += " [88] Delay Time Ratio Left: "; dataBytePos += 3; break;
961 case 0x54: str += " [88] Delay Time Ratio Right: "; dataBytePos += 3; break;
962 case 0x55: str += " [88] Delay Level Center: "; dataBytePos += 3; break;
963 case 0x56: str += " [88] Delay Level Left: "; dataBytePos += 3; break;
964 case 0x57: str += " [88] Delay Level Right: "; dataBytePos += 3; break;
965 case 0x58: str += " [88] Delay Level: "; dataBytePos += 3; break;
966 case 0x59: str += " [88] Delay FB: "; dataBytePos += 3; break;
967 case 0x5A: str += " [88] Delay Send Level To Reverb: "; dataBytePos += 3; break;
970 else if( msgdata[5]==0x02 ) {
971 switch( msgdata[6] ) {
972 case 0x00: str += " [88] EQ Low Freq: "; dataBytePos += 3; break;
973 case 0x01: str += " [88] EQ Low Gain: "; dataBytePos += 3; break;
974 case 0x02: str += " [88] EQ High Freq: "; dataBytePos += 3; break;
975 case 0x03: str += " [88] EQ High Gain: "; dataBytePos += 3; break;
978 else if( msgdata[5]==0x03 ) {
979 if( msgdata[6] == 0x00 ) {
980 str += " [Pro] EFX Type: "; dataBytePos += 3;
982 else if( msgdata[6] >= 0x03 && msgdata[6] <= 0x16 ) {
983 str += String.format(" [Pro] EFX Param %d", msgdata[6]-2 );
986 else if( msgdata[6] == 0x17 ) {
987 str += " [Pro] EFX Send Level To Reverb: "; dataBytePos += 3;
989 else if( msgdata[6] == 0x18 ) {
990 str += " [Pro] EFX Send Level To Chorus: "; dataBytePos += 3;
992 else if( msgdata[6] == 0x19 ) {
993 str += " [Pro] EFX Send Level To Delay: "; dataBytePos += 3;
995 else if( msgdata[6] == 0x1B ) {
996 str += " [Pro] EFX Ctrl Src1: "; dataBytePos += 3;
998 else if( msgdata[6] == 0x1C ) {
999 str += " [Pro] EFX Ctrl Depth1: "; dataBytePos += 3;
1001 else if( msgdata[6] == 0x1D ) {
1002 str += " [Pro] EFX Ctrl Src2: "; dataBytePos += 3;
1004 else if( msgdata[6] == 0x1E ) {
1005 str += " [Pro] EFX Ctrl Depth2: "; dataBytePos += 3;
1007 else if( msgdata[6] == 0x1F ) {
1008 str += " [Pro] EFX Send EQ Switch: "; dataBytePos += 3;
1011 else if( (msgdata[5] & 0xF0) == 0x10 ) {
1012 int ch = (msgdata[5] & 0x0F);
1013 if( ch <= 9 ) ch--; else if( ch == 0 ) ch = 9;
1014 if( msgdata[6]==0x02 ) {
1015 str += String.format(
1016 " Rx Ch: Part=%d(0x%02X) Ch=0x%02X", (ch+1), msgdata[5], msgdata[7]
1020 else if( msgdata[6]==0x15 ) {
1022 switch( msgdata[7] ) {
1023 case 0: map = " NormalPart"; break;
1024 case 1: map = " DrumMap1"; break;
1025 case 2: map = " DrumMap2"; break;
1026 default: map = String.format("0x%02X",msgdata[7]); break;
1028 str += String.format(
1029 " Rhythm Part: Ch=%d(0x%02X) Map=%s",
1036 else if( (msgdata[5] & 0xF0) == 0x40 ) {
1037 int ch = (msgdata[5] & 0x0F);
1038 if( ch <= 9 ) ch--; else if( ch == 0 ) ch = 9;
1039 int dt = (msgdata[7] & 0xFF);
1040 if( msgdata[6]==0x20 ) {
1041 str += String.format(
1042 " [88] EQ: Ch=%d(0x%02X) %s",
1044 dt==0 ? "OFF" : dt==1 ? "ON" : String.format("0x%02X",dt)
1047 else if( msgdata[6]==0x22 ) {
1048 str += String.format(
1049 " [Pro] Part EFX Assign: Ch=%d(0x%02X) %s",
1051 dt==0 ? "ByPass" : dt==1 ? "EFX" : String.format("0x%02X",dt)
1060 str += " [GS-LCD]"; dataBytePos++;
1061 if( msgdata[3]==0x12 ) {
1062 str += " [DT1]"; dataBytePos++;
1063 if( msgdata[4]==0x10 && msgdata[5]==0x00 && msgdata[6]==0x00 ) {
1065 str += " Disp [" +(new String(
1066 msgdata, dataBytePos, msgdata.length - dataBytePos - 2
1071 case 0x14: str += " [D-50]"; dataBytePos++; break;
1072 case 0x16: str += " [MT-32]"; dataBytePos++; break;
1075 case 0x43: // Yamaha
1076 if( (deviceId & 0xF0) == 0x10 && modelId == 0x4C ) {
1077 str += " [XG]Dev#="+(deviceId & 0x0F);
1079 if( msgdata[3]==0 && msgdata[4]==0 && msgdata[5]==0x7E && msgdata[6]==0 ) {
1080 str += " System ON";
1085 else if( deviceId == 0x79 && modelId == 9 ) {
1086 str += " [eVocaloid]";
1088 if( msgdata[3]==0x11 && msgdata[4]==0x0A && msgdata[5]==0 ) {
1089 StringBuilder p = new StringBuilder();
1090 for( int i=6; i<msgdata.length; i++ ) {
1091 int b = (msgdata[i] & 0xFF);
1092 if( b == 0xF7 ) break;
1094 b >= MIDISpec.nsx39LyricElements.length ?
1095 "?": MIDISpec.nsx39LyricElements[b]
1099 str += " pronounce["+p+"]";
1109 for( i = dataBytePos; i<msgdata.length-1; i++ ) {
1110 str += String.format( " %02X", msgdata[i] );
1112 if( i < msgdata.length && (int)(msgdata[i] & 0xFF) != 0xF7 ) {
1113 str+=" [ Invalid EOX " + String.format( "%02X", msgdata[i] ) + " ]";
1119 for( byte b : msg.getMessage() ) str += String.format( " %02X", b );
1123 public static boolean isRhythmPart(int ch) { return (ch == 9); }