OSDN Git Service

592100c1b2e5cc61fdb8fa371c5a3b0c858bf651
[midichordhelper/MIDIChordHelper.git] / src / camidion / chordhelper / music / MIDISpec.java
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;
6 import java.util.Map;
7 import java.util.Objects;
8
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;
17 /**
18  * MIDI仕様(システムエクスクルーシブ含む)
19  */
20 public class MIDISpec {
21         public static final int MAX_CHANNELS = 16;
22         public static final int PITCH_BEND_NONE = 8192;
23         /**
24          * 指定されたMIDIノート番号の音の周波数(A=440Hzチューニング時)を返します。
25          * @param noteNumber MIDIノート番号
26          * @return 音の周波数[Hz]
27          */
28         public static double noteNumberToFrequency(int noteNumber) {
29                 return 55 * Math.pow( 2, (double)(noteNumber - 33)/12 );
30         }
31         /**
32          * メタメッセージタイプ名マップ
33          */
34         private static final Map<Integer,String>
35                 META_MESSAGE_TYPE_NAMES = new HashMap<Integer,String>() {
36                         {
37                                 put(0x00, "Seq Number");
38                                 put(0x01, "Text");
39                                 put(0x02, "Copyright");
40                                 put(0x03, "Seq/Track Name");
41                                 put(0x04, "Instrument Name");
42                                 put(0x05, "Lyric");
43                                 put(0x06, "Marker");
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");
50                                 put(0x51, "Tempo");
51                                 put(0x54, "SMPTE Offset");
52                                 put(0x58, "Time Signature");
53                                 put(0x59, "Key Signature");
54                                 put(0x7F, "Sequencer Specific");
55                         }
56                 };
57         /**
58          * メタメッセージタイプの名前を返します。
59          * @param metaMessageType メタメッセージタイプ
60          * @return メタメッセージタイプの名前
61          */
62         public static String getMetaName(int metaMessageType) {
63                 return META_MESSAGE_TYPE_NAMES.get(metaMessageType);
64         }
65         /**
66          * メタメッセージタイプがテキストのつくものか調べます。
67          * @param metaMessageType メタメッセージタイプ
68          * @return テキストがつくときtrue
69          */
70         public static boolean hasMetaMessageText(int metaMessageType) {
71                 return (metaMessageType > 0 && metaMessageType < 10);
72         }
73         /**
74          * メタメッセージタイプが拍子記号か調べます。
75          * @param metaMessageType メタメッセージタイプ
76          * @return 拍子記号ならtrue
77          */
78         public static boolean isTimeSignature(int metaMessageType) {
79                 return metaMessageType == 0x58;
80         }
81         /**
82          * MIDIメッセージが拍子記号か調べます。
83          * @param msg MIDIメッセージ
84          * @return 拍子記号ならtrue
85          */
86         public static boolean isTimeSignature(MidiMessage midiMessage) {
87                 return (midiMessage instanceof MetaMessage) && isTimeSignature(((MetaMessage)midiMessage).getType());
88         }
89         /**
90          * メタメッセージタイプが EOT (End Of Track) か調べます。
91          * @param metaMessageType メタメッセージタイプ
92          * @return EOTならtrue
93          */
94         public static boolean isEOT(int metaMessageType) { return metaMessageType == 0x2F; }
95         /**
96          * MIDIメッセージが EOT (End Of Track) か調べます。
97          * @param midiMessage MIDIメッセージ
98          * @return EOTならtrue
99          */
100         public static boolean isEOT(MidiMessage midiMessage) {
101                 return (midiMessage instanceof MetaMessage) && isEOT(((MetaMessage)midiMessage).getType());
102         }
103         /**
104          * 1分のマイクロ秒数
105          */
106         public static final int MICROSECOND_PER_MINUTE = (60 * 1000 * 1000);
107         /**
108          * MIDIのテンポメッセージについているバイト列をQPM単位のテンポに変換します。
109          * @param b バイト列
110          * @return テンポ[QPM]
111          */
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;
115         }
116         /**
117          * QPM単位のテンポをMIDIのテンポメッセージ用バイト列に変換します。
118          * @param qpm テンポ[QPM]
119          * @return MIDIのテンポメッセージ用バイト列
120          */
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);
127                 return b;
128         }
129         /**
130          * トラック名のメタメッセージを返します。
131          * @param track 対象MIDIトラック
132          * @return トラック名のメタメッセージ(見つからない場合はnull)
133          */
134         public static MetaMessage getNameMessageOf(Track track) {
135                 MidiEvent midiEvent;
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
139                                 break;
140                         }
141                         MidiMessage message = midiEvent.getMessage();
142                         if( ! (message instanceof MetaMessage) ) continue;
143                         MetaMessage metaMessage = (MetaMessage)message;
144                         if( metaMessage.getType() == 0x03 ) return metaMessage;
145                 }
146                 return null;
147         }
148         /**
149          * トラック名のバイト列を返します。
150          * @param track MIDIトラック
151          * @return トラック名のバイト列(トラック名が見つからない場合はnull)
152          */
153         public static byte[] getNameBytesOf(Track track) {
154                 MetaMessage metaMessage = getNameMessageOf(track);
155                 return metaMessage != null ? metaMessage.getData() : null;
156         }
157         /**
158          * トラック名のバイト列を設定します。
159          * @param track MIDIトラック
160          * @param name トラック名
161          * @return 成功:true、失敗:false
162          */
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));
167                 }
168                 try {
169                         metaMsg.setMessage(0x03, name, name.length);
170                 }
171                 catch( InvalidMidiDataException e ) {
172                         e.printStackTrace();
173                         return false;
174                 }
175                 return true;
176         }
177         /**
178          * シーケンス名のバイト列を返します。
179          * <p>トラック名の入った最初のトラックにあるトラック名をシーケンス名として返します。</p>
180          * @param sequence MIDIシーケンス
181          * @return シーケンス名のバイト列(見つからない場合はnull)
182          */
183         public static byte[] getNameBytesOf(Sequence sequence) {
184                 return Arrays.stream(sequence.getTracks()).map(t->getNameBytesOf(t))
185                         .filter(Objects::nonNull).findFirst().orElse(null);
186         }
187         /**
188          * シーケンス名のバイト列を設定します。
189          * <p>先頭のトラックに設定されます。設定に失敗した場合、順に次のトラックへの設定を試みます。
190          * </p>
191          *
192          * @param sequence MIDIシーケンス
193          * @param name シーケンス名のバイト列
194          * @return 成功:true、失敗:false
195          */
196         public static boolean setNameBytesOf(Sequence sequence, byte[] name) {
197                 return Arrays.stream(sequence.getTracks()).anyMatch(t->setNameBytesOf(t,name));
198         }
199         /**
200          * シーケンスの名前や歌詞など、メタイベントのテキストをもとに文字コードを判定します。
201          * 判定できなかった場合はnullを返します。
202          * @param sequence MIDIシーケンス
203          * @return 文字コード判定結果(またはnull)
204          */
205         public static Charset getCharsetOf(Sequence sequence) {
206                 byte[] b = new byte[0];
207                 for( Track track : sequence.getTracks() ) {
208                         MidiMessage message;
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);
219                                 b = concated;
220                         }
221                 }
222                 if( b.length <= 0 ) return null;
223                 final byte[] fixedBytes = b;
224                 try {
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) {
230                         e.printStackTrace();
231                         return null;
232                 }
233         }
234         ///////////////////////////////////////////////////////////////////
235         //
236         // Channel Message / System Message
237         //
238         /**
239          * MIDIステータス名を返します。
240          * @param status MIDIステータス
241          * @return MIDIステータス名
242          */
243         public static String getStatusName( int status ) {
244                 if( status < 0x80 ) {
245                         // No such status
246                         return null;
247                 }
248                 else if ( status < 0xF0 ) {
249                         // Channel Message
250                         return ch_msg_status_names[ (status >> 4) - 0x08 ];
251                 }
252                 else if ( status <= 0xFF ) {
253                         // System Message
254                         return sys_msg_names[ status - 0xF0 ];
255                 }
256                 return null;
257         }
258         /**
259          * 指定のMIDIショートメッセージがチャンネルメッセージかどうか調べます。
260          * @param msg MIDIメッセージ
261          * @return MIDIチャンネルメッセージの場合true
262          */
263         public static boolean isChannelMessage( ShortMessage msg ) {
264                 return isChannelMessage( msg.getStatus() );
265         }
266         /**
267          * MIDIステータスがチャンネルメッセージかどうか調べます。
268          * @param status MIDIステータス
269          * @return MIDIチャンネルメッセージの場合true
270          */
271         public static boolean isChannelMessage( int status ) {
272                 return ( status < 0xF0 && status >= 0x80 );
273         }
274         private static final String ch_msg_status_names[] = {
275                 // 0x80 - 0xE0 : Channel Voice Message
276                 // 0xB0 : Channel Mode Message
277                 "NoteOFF", "NoteON",
278                 "Polyphonic Key Pressure", "Ctrl/Mode",
279                 "Program", "Ch.Pressure", "Pitch Bend"
280         };
281         private static final String sys_msg_names[] = {
282                 // 0xF0 : System Exclusive
283                 "SysEx",
284                 //
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",
289                 //
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",
294         };
295         ///////////////////////////////////////////////////////////////////
296         //
297         // Control Change / Channel Mode Message
298         //
299         /**
300          * コントロールチェンジの名前を返します。
301          * @param controllerNumber コントローラ番号
302          * @return コントロールチェンジの名前
303          */
304         public static String getControllerName( int controllerNumber ) {
305                 if( controllerNumber < 0x00 ) {
306                         return null;
307                 }
308                 else if( controllerNumber < 0x20 ) {
309                         String s = controllerNames0[controllerNumber];
310                         if( s != null ) s += " (MSB)";
311                         return s;
312                 }
313                 else if( controllerNumber < 0x40 ) {
314                         String s = controllerNames0[controllerNumber - 0x20];
315                         if( s != null ) s += " (LSB)";
316                         return s;
317                 }
318                 else if( controllerNumber < 0x78 ) {
319                         return controllerMomentarySwitchNames[controllerNumber - 0x40];
320                 }
321                 else if( controllerNumber < 0x80 ) {
322                         return controllerModeMessageNames[controllerNumber - 0x78];
323                 }
324                 else {
325                         return null;
326                 }
327         }
328         private static final String controllerNames0[] = {
329                 //
330                 // 0x00-0x0F (MSB)
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,
335                 //
336                 // 0x10-0x1F (MSB)
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,
342                 //
343                 // 0x20-0x3F (LSB)
344         };
345         private static final String controllerMomentarySwitchNames[] = {
346                 //
347                 // 0x40-0x4F
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)",
361                 //
362                 // 0x50-0x5F
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",
369                 //
370                 // 0x60-0x6F
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,
376                 //
377                 // 0x70-0x77
378                 null, null, null, null,
379                 null, null, null, null
380         };
381         private static final String controllerModeMessageNames[] = {
382                 // 0x78-0x7F
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"
387         };
388         ///////////////////////////////////////////////////////////////////
389         //
390         // System Exclusive
391         //
392         /**
393          * システムエクスクルーシブの製造者IDをキーにして製造者名を返すマップ
394          */
395         public static final Map<Integer,String>
396                 SYSEX_MANUFACTURER_NAMES = new HashMap<Integer,String>() {
397                         {
398                                 put(0x40,"KAWAI");
399                                 put(0x41,"Roland");
400                                 put(0x42,"KORG");
401                                 put(0x43,"YAMAHA");
402                                 put(0x44,"CASIO");
403                                 put(0x7D,"Non-Commercial");
404                                 put(0x7E,"Universal: Non-RealTime");
405                                 put(0x7F,"Universal: RealTime");
406                         }
407                 };
408         /**
409          * MIDIノート番号の最大値
410          */
411         public static final int MAX_NOTE_NO = 127;
412         /**
413          * General MIDI の楽器ファミリー名の配列
414          */
415         public static final String instrumentFamilyNames[] = {
416
417                 "Piano",
418                 "Chrom.Percussion",
419                 "Organ",
420                 "Guitar",
421                 "Bass",
422                 "Strings",
423                 "Ensemble",
424                 "Brass",
425
426                 "Reed",
427                 "Pipe",
428                 "Synth Lead",
429                 "Synth Pad",
430                 "Synth Effects",
431                 "Ethnic",
432                 "Percussive",
433                 "Sound Effects",
434         };
435         /**
436          * General MIDI の楽器名(プログラムチェンジのプログラム名)の配列
437          */
438         public static final String instrumentNames[] = {
439                 "Acoustic Grand Piano",
440                 "Bright Acoustic Piano",
441                 "Electric Grand Piano",
442                 "Honky-tonk Piano",
443                 "Electric Piano 1",
444                 "Electric Piano 2",
445                 "Harpsichord",
446                 "Clavi",
447                 "Celesta",
448                 "Glockenspiel",
449                 "Music Box",
450                 "Vibraphone",
451                 "Marimba",
452                 "Xylophone",
453                 "Tubular Bells",
454                 "Dulcimer",
455                 "Drawbar Organ",
456                 "Percussive Organ",
457                 "Rock Organ",
458                 "Church Organ",
459                 "Reed Organ",
460                 "Accordion",
461                 "Harmonica",
462                 "Tango Accordion",
463                 "Acoustic Guitar (nylon)",
464                 "Acoustic Guitar (steel)",
465                 "Electric Guitar (jazz)",
466                 "Electric Guitar (clean)",
467                 "Electric Guitar (muted)",
468                 "Overdriven Guitar",
469                 "Distortion Guitar",
470                 "Guitar harmonics",
471                 "Acoustic Bass",
472                 "Electric Bass (finger)",
473                 "Electric Bass (pick)",
474                 "Fretless Bass",
475                 "Slap Bass 1",
476                 "Slap Bass 2",
477                 "Synth Bass 1",
478                 "Synth Bass 2",
479                 "Violin",
480                 "Viola",
481                 "Cello",
482                 "Contrabass",
483                 "Tremolo Strings",
484                 "Pizzicato Strings",
485                 "Orchestral Harp",
486                 "Timpani",
487                 "String Ensemble 1",
488                 "String Ensemble 2",
489                 "SynthStrings 1",
490                 "SynthStrings 2",
491                 "Choir Aahs",
492                 "Voice Oohs",
493                 "Synth Voice",
494                 "Orchestra Hit",
495                 "Trumpet",
496                 "Trombone",
497                 "Tuba",
498                 "Muted Trumpet",
499                 "French Horn",
500                 "Brass Section",
501                 "SynthBrass 1",
502                 "SynthBrass 2",
503                 "Soprano Sax",
504                 "Alto Sax",
505                 "Tenor Sax",
506                 "Baritone Sax",
507                 "Oboe",
508                 "English Horn",
509                 "Bassoon",
510                 "Clarinet",
511                 "Piccolo",
512                 "Flute",
513                 "Recorder",
514                 "Pan Flute",
515                 "Blown Bottle",
516                 "Shakuhachi",
517                 "Whistle",
518                 "Ocarina",
519                 "Lead 1 (square)",
520                 "Lead 2 (sawtooth)",
521                 "Lead 3 (calliope)",
522                 "Lead 4 (chiff)",
523                 "Lead 5 (charang)",
524                 "Lead 6 (voice)",
525                 "Lead 7 (fifths)",
526                 "Lead 8 (bass + lead)",
527                 "Pad 1 (new age)",
528                 "Pad 2 (warm)",
529                 "Pad 3 (polysynth)",
530                 "Pad 4 (choir)",
531                 "Pad 5 (bowed)",
532                 "Pad 6 (metallic)",
533                 "Pad 7 (halo)",
534                 "Pad 8 (sweep)",
535                 "FX 1 (rain)",
536                 "FX 2 (soundtrack)",
537                 "FX 3 (crystal)",
538                 "FX 4 (atmosphere)",
539                 "FX 5 (brightness)",
540                 "FX 6 (goblins)",
541                 "FX 7 (echoes)",
542                 "FX 8 (sci-fi)",
543                 "Sitar",
544                 "Banjo",
545                 "Shamisen",
546                 "Koto",
547                 "Kalimba",
548                 "Bag pipe",
549                 "Fiddle",
550                 "Shanai",
551                 "Tinkle Bell",
552                 "Agogo",
553                 "Steel Drums",
554                 "Woodblock",
555                 "Taiko Drum",
556                 "Melodic Tom",
557                 "Synth Drum",
558                 "Reverse Cymbal",
559                 "Guitar Fret Noise",
560                 "Breath Noise",
561                 "Seashore",
562                 "Bird Tweet",
563                 "Telephone Ring",
564                 "Helicopter",
565                 "Applause",
566                 "Gunshot",
567         };
568         /**
569          * パーカッション用MIDIノート番号の最小値
570          */
571         public static final int MIN_PERCUSSION_NUMBER = 35;
572         /**
573          * パーカッション用のMIDIチャンネル(通常はCH.10)における
574          * ノート番号からパーカッション名を返します。
575          *
576          * @param note_no ノート番号
577          * @return パーカッション名
578          */
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)" ;
582         }
583         public static final String      PERCUSSION_NAMES[] = {
584                 "Acoustic Bass Drum",
585                 "Bass Drum 1",
586                 "Side Stick",
587                 "Acoustic Snare",
588                 "Hand Clap",
589                 "Electric Snare",
590                 "Low Floor Tom",
591                 "Closed Hi Hat",
592                 "High Floor Tom",
593                 "Pedal Hi-Hat",
594                 "Low Tom",
595                 "Open Hi-Hat",
596                 "Low-Mid Tom",
597                 "Hi Mid Tom",
598                 "Crash Cymbal 1",
599                 "High Tom",
600                 "Ride Cymbal 1",
601                 "Chinese Cymbal",
602                 "Ride Bell",
603                 "Tambourine",
604                 "Splash Cymbal",
605                 "Cowbell",
606                 "Crash Cymbal 2",
607                 "Vibraslap",
608                 "Ride Cymbal 2",
609                 "Hi Bongo",
610                 "Low Bongo",
611                 "Mute Hi Conga",
612                 "Open Hi Conga",
613                 "Low Conga",
614                 "High Timbale",
615                 "Low Timbale",
616                 "High Agogo",
617                 "Low Agogo",
618                 "Cabasa",
619                 "Maracas",
620                 "Short Whistle",
621                 "Long Whistle",
622                 "Short Guiro",
623                 "Long Guiro",
624                 "Claves",
625                 "Hi Wood Block",
626                 "Low Wood Block",
627                 "Mute Cuica",
628                 "Open Cuica",
629                 "Mute Triangle",
630                 "Open Triangle",
631         };
632         public static final String nsx39LyricElements[] = {
633                 "あ","い","う","え","お",
634                 "か","き","く","け","こ",
635                 "が","ぎ","ぐ","げ","ご",
636             "きゃ","きゅ","きょ",
637             "ぎゃ","ぎゅ","ぎょ",
638                 "さ","すぃ","す","せ","そ",
639                 "ざ","ずぃ","ず","ぜ","ぞ",
640             "しゃ","し","しゅ","しぇ","しょ",
641             "じゃ","じ","じゅ","じぇ","じょ",
642                 "た","てぃ","とぅ","て","と",
643                 "だ","でぃ","どぅ","で","ど",
644                 "てゅ","でゅ",
645                 "ちゃ","ち","ちゅ","ちぇ","ちょ",
646                 "つぁ","つぃ","つ","つぇ","つぉ",
647                 "な","に","ぬ","ね","の",
648             "にゃ","にゅ","にょ",
649                 "は","ひ","ふ","へ","ほ",
650                 "ば","び","ぶ","べ","ぼ",
651                 "ぱ","ぴ","ぷ","ぺ","ぽ",
652                 "ひゃ","ひゅ","ひょ",
653                 "びゃ","びゅ","びょ",
654                 "ぴゃ","ぴゅ","ぴょ",
655                 "ふぁ","ふぃ","ふゅ","ふぇ","ふぉ",
656                 "ま","み","む","め","も",
657                 "みゃ","みゅ","みょ",
658                 "や","ゆ","よ",
659                 "ら","り","る","れ","ろ",
660                 "りゃ","りゅ","りょ",
661                 "わ","うぃ","うぇ","を",
662                 "ん","ん","ん","ん","ん",
663         };
664         /**
665          * MIDIメッセージの内容を文字列で返します。
666          * @param msg MIDIメッセージ
667          * @param charset MIDIメタメッセージに含まれるテキストデータの文字コード
668          * @return MIDIメッセージの内容を表す文字列
669          */
670         public static String msgToString(MidiMessage msg, Charset charset) {
671                 String str = "";
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
683                                 ) + ": ";
684                                 int cmd = shortmsg.getCommand();
685                                 switch( cmd ) {
686                                 case ShortMessage.NOTE_OFF:
687                                 case ShortMessage.NOTE_ON:
688                                         str += channelPrefix + statusPrefix + data1;
689                                         str += ":[";
690                                         if( MIDISpec.isRhythmPart(channel) ) {
691                                                 str += getPercussionName(data1);
692                                         }
693                                         else {
694                                                 str += Note.noteNumberToSymbol(data1);
695                                         }
696                                         str +="] Velocity=" + data2;
697                                         break;
698                                 case ShortMessage.POLY_PRESSURE:
699                                         str += channelPrefix + statusPrefix + "Note=" + data1 + " Pressure=" + data2;
700                                         break;
701                                 case ShortMessage.PROGRAM_CHANGE:
702                                         str += channelPrefix + statusPrefix + data1 + ":[" + instrumentNames[data1] + "]";
703                                         if( data2 != 0 ) str += " data2=" + data2;
704                                         break;
705                                 case ShortMessage.CHANNEL_PRESSURE:
706                                         str += channelPrefix + statusPrefix + data1;
707                                         if( data2 != 0 ) str += " data2=" + data2;
708                                         break;
709                                 case ShortMessage.PITCH_BEND:
710                                 {
711                                         int val = ((data1 & 0x7F) | ((data2 & 0x7F) << 7));
712                                         str += channelPrefix + statusPrefix + ( (val-8192) * 100 / 8191) + "% (" + val + ")";
713                                 }
714                                 break;
715                                 case ShortMessage.CONTROL_CHANGE:
716                                 {
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;
722                                                 return str;
723                                         }
724                                         str += ctrl_name;
725                                         //
726                                         // Controller's value
727                                         switch( data1 ) {
728                                         case 0x40: case 0x41: case 0x42: case 0x43: case 0x45:
729                                                 str += " " + ( data2==0x3F?"OFF":data2==0x40?"ON":data2 );
730                                                 break;
731                                         case 0x44: // Legato Footswitch
732                                                 str += " " + ( data2==0x3F?"Normal":data2==0x40?"Legato":data2 );
733                                                 break;
734                                         case 0x7A: // Local Control
735                                                 str += " " + ( data2==0x00?"OFF":data2==0x7F?"ON":data2 );
736                                                 break;
737                                         default:
738                                                 str += " " + data2;
739                                                 break;
740                                         }
741                                 }
742                                 break;
743
744                                 default:
745                                         // Never reached here
746                                         break;
747                                 }
748                         }
749                         else { // System Message
750                                 str += (statusName == null ? ("status="+status) : statusName );
751                                 str += " (" + data1 + "," + data2 + ")";
752                         }
753                         return str;
754                 }
755                 else if( msg instanceof MetaMessage ) {
756                         MetaMessage metamsg = (MetaMessage)msg;
757                         byte[] msgdata = metamsg.getData();
758                         int msgtype = metamsg.getType();
759                         str += "Meta: ";
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 );
764                                 str += " )";
765                                 return str;
766                         }
767                         // Add the message type name
768                         str += meta_name;
769                         //
770                         // Add the text data
771                         if( hasMetaMessageText(msgtype) ) {
772                                 str +=" ["+(new String(msgdata,charset))+"]";
773                                 return str;
774                         }
775                         // Add the numeric data
776                         switch(msgtype) {
777                         case 0x00: // Sequence Number (for MIDI Format 2)
778                                 if( msgdata.length == 2 ) {
779                                         str += String.format(
780                                                 ": %04X",
781                                                 ((msgdata[0] & 0xFF) << 8) | (msgdata[1] & 0xFF)
782                                         );
783                                         break;
784                                 }
785                                 str += ": Size not 2 byte : data=(";
786                                 for( byte b : msgdata ) str += String.format( " %02X", b );
787                                 str += " )";
788                                 break;
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 );
793                                         break;
794                                 }
795                                 str += ": Size not 1 byte : data=(";
796                                 for( byte b : msgdata ) str += String.format( " %02X", b );
797                                 str += " )";
798                                 break;
799                         case 0x51: // Tempo
800                                 str += ": " + byteArrayToQpmTempo( msgdata ) + "[QPM] (";
801                                 for( byte b : msgdata ) str += String.format( " %02X", b );
802                                 str += " )";
803                                 break;
804                         case 0x54: // SMPTE Offset
805                                 if( msgdata.length == 5 ) {
806                                         str += ": "
807                                                 + (msgdata[0] & 0xFF) + ":"
808                                                 + (msgdata[1] & 0xFF) + ":"
809                                                 + (msgdata[2] & 0xFF) + "."
810                                                 + (msgdata[3] & 0xFF) + "."
811                                                 + (msgdata[4] & 0xFF);
812                                         break;
813                                 }
814                                 str += ": Size not 5 byte : data=(";
815                                 for( byte b : msgdata ) str += String.format( " %02X", b );
816                                 str += " )";
817                                 break;
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]";
822                                         break;
823                                 }
824                                 str += ": Size not 4 byte : data=(";
825                                 for( byte b : msgdata ) str += String.format( " %02X", b );
826                                 str += " )";
827                                 break;
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) + ")";
833                                         break;
834                                 }
835                                 str += ": Size not 2 byte : data=(";
836                                 for( byte b : msgdata ) str += String.format( " %02X", b );
837                                 str += " )";
838                                 break;
839                         case 0x7F: // Sequencer Specific Meta Event
840                                 str += " (";
841                                 for( byte b : msgdata ) str += String.format( " %02X", b );
842                                 str += " )";
843                                 break;
844                         }
845                         return str;
846                 }
847                 else if( msg instanceof SysexMessage ) {
848                         SysexMessage sysexmsg = (SysexMessage)msg;
849                         int status = sysexmsg.getStatus();
850                         byte[] msgdata = sysexmsg.getData();
851                         int dataBytePos = 1;
852                         switch( status ) {
853                         case SysexMessage.SYSTEM_EXCLUSIVE:
854                                 str += "SysEx: ";
855                                 break;
856                         case SysexMessage.SPECIAL_SYSTEM_EXCLUSIVE:
857                                 str += "SysEx(Special): ";
858                                 break;
859                         default:
860                                 str += "SysEx: Invalid (status="+status+") ";
861                                 break;
862                         }
863                         if( msgdata.length < 1 ) {
864                                 str += " Invalid data size: " + msgdata.length;
865                                 return str;
866                         }
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]);
873                         }
874                         str += manufacturerName + String.format(" (DevID=0x%02X)", deviceId);
875                         switch( manufacturerId ) {
876                         case 0x7E: // Non-Realtime Universal
877                                 dataBytePos++;
878                                 int sub_id_1 = modelId;
879                                 int sub_id_2 = (int)(msgdata[3] & 0xFF);
880                                 switch( sub_id_1 ) {
881                                 case 0x09: // General MIDI (GM)
882                                         switch( sub_id_2 ) {
883                                         case 0x01: str += " GM System ON"; return str;
884                                         case 0x02: str += " GM System OFF"; return str;
885                                         }
886                                         break;
887                                 default:
888                                         break;
889                                 }
890                                 break;
891                                 // case 0x7F: // Realtime Universal
892                         case 0x41: // Roland
893                                 dataBytePos++;
894                                 switch( modelId ) {
895                                 case 0x42:
896                                         str += " [GS]"; dataBytePos++;
897                                         if( msgdata[3]==0x12 ) {
898                                                 str += "DT1:"; dataBytePos++;
899                                                 switch( msgdata[4] ) {
900                                                 case 0x00:
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;
905                                                                         }
906                                                                         else if( msgdata[7]==0x01 ) {
907                                                                                 str += " [88] System Mode Set (Mode 2: Double Module)"; return str;
908                                                                         }
909                                                                 }
910                                                         }
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",
915                                                                                 msgdata[6],
916                                                                                 port==0?"A":port==1?"B":String.format("0x%02X",port)
917                                                                                 );
918                                                                 return str;
919                                                         }
920                                                         break;
921                                                 case 0x40:
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;
928                                                                 case 0x7F:
929                                                                         switch( msgdata[7] ) {
930                                                                         case 0x00: str += " GS Reset"; return str;
931                                                                         case 0x7F: str += " Exit GS Mode"; return str;
932                                                                         }
933                                                                         break;
934                                                                 }
935                                                         }
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;
968                                                                 }
969                                                         }
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;
976                                                                 }
977                                                         }
978                                                         else if( msgdata[5]==0x03 ) {
979                                                                 if( msgdata[6] == 0x00 ) {
980                                                                         str += " [Pro] EFX Type: "; dataBytePos += 3;
981                                                                 }
982                                                                 else if( msgdata[6] >= 0x03 && msgdata[6] <= 0x16 ) {
983                                                                         str += String.format(" [Pro] EFX Param %d", msgdata[6]-2 );
984                                                                         dataBytePos += 3;
985                                                                 }
986                                                                 else if( msgdata[6] == 0x17 ) {
987                                                                         str += " [Pro] EFX Send Level To Reverb: "; dataBytePos += 3;
988                                                                 }
989                                                                 else if( msgdata[6] == 0x18 ) {
990                                                                         str += " [Pro] EFX Send Level To Chorus: "; dataBytePos += 3;
991                                                                 }
992                                                                 else if( msgdata[6] == 0x19 ) {
993                                                                         str += " [Pro] EFX Send Level To Delay: "; dataBytePos += 3;
994                                                                 }
995                                                                 else if( msgdata[6] == 0x1B ) {
996                                                                         str += " [Pro] EFX Ctrl Src1: "; dataBytePos += 3;
997                                                                 }
998                                                                 else if( msgdata[6] == 0x1C ) {
999                                                                         str += " [Pro] EFX Ctrl Depth1: "; dataBytePos += 3;
1000                                                                 }
1001                                                                 else if( msgdata[6] == 0x1D ) {
1002                                                                         str += " [Pro] EFX Ctrl Src2: "; dataBytePos += 3;
1003                                                                 }
1004                                                                 else if( msgdata[6] == 0x1E ) {
1005                                                                         str += " [Pro] EFX Ctrl Depth2: "; dataBytePos += 3;
1006                                                                 }
1007                                                                 else if( msgdata[6] == 0x1F ) {
1008                                                                         str += " [Pro] EFX Send EQ Switch: "; dataBytePos += 3;
1009                                                                 }
1010                                                         }
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]
1017                                                                                         );
1018                                                                         return str;
1019                                                                 }
1020                                                                 else if( msgdata[6]==0x15 ) {
1021                                                                         String map;
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;
1027                                                                         }
1028                                                                         str += String.format(
1029                                                                                 " Rhythm Part: Ch=%d(0x%02X) Map=%s",
1030                                                                                 (ch+1), msgdata[5],
1031                                                                                 map
1032                                                                         );
1033                                                                         return str;
1034                                                                 }
1035                                                         }
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",
1043                                                                                 (ch+1), msgdata[5],
1044                                                                                 dt==0 ? "OFF" : dt==1 ? "ON" : String.format("0x%02X",dt)
1045                                                                         );
1046                                                                 }
1047                                                                 else if( msgdata[6]==0x22 ) {
1048                                                                         str += String.format(
1049                                                                                 " [Pro] Part EFX Assign: Ch=%d(0x%02X) %s",
1050                                                                                 (ch+1), msgdata[5],
1051                                                                                 dt==0 ? "ByPass" : dt==1 ? "EFX" : String.format("0x%02X",dt)
1052                                                                         );
1053                                                                 }
1054                                                         }
1055                                                         break;
1056                                                 } // [4]
1057                                         } // [3] [DT1]
1058                                         break; // [GS]
1059                                 case 0x45:
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 ) {
1064                                                         dataBytePos += 3;
1065                                                         str += " Disp [" +(new String(
1066                                                                 msgdata, dataBytePos, msgdata.length - dataBytePos - 2
1067                                                         ))+ "]";
1068                                                 }
1069                                         } // [3] [DT1]
1070                                         break;
1071                                 case 0x14: str += " [D-50]"; dataBytePos++; break;
1072                                 case 0x16: str += " [MT-32]"; dataBytePos++; break;
1073                                 } // [2] model_id
1074                                 break;
1075                         case 0x43: // Yamaha
1076                                 if( (deviceId & 0xF0) == 0x10 && modelId == 0x4C ) {
1077                                         str += " [XG]Dev#="+(deviceId & 0x0F);
1078                                         dataBytePos += 2;
1079                                         if( msgdata[3]==0 && msgdata[4]==0 && msgdata[5]==0x7E && msgdata[6]==0 ) {
1080                                                 str += " System ON";
1081                                                 return str;
1082                                         }
1083
1084                                 }
1085                                 else if( deviceId == 0x79 && modelId == 9 ) {
1086                                         str += " [eVocaloid]";
1087                                         dataBytePos += 2;
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;
1093                                                         String s = (
1094                                                                 b >= MIDISpec.nsx39LyricElements.length ?
1095                                                                 "?": MIDISpec.nsx39LyricElements[b]
1096                                                         );
1097                                                         p.append(s);
1098                                                 }
1099                                                 str += " pronounce["+p+"]";
1100                                                 return str;
1101                                         }
1102                                 }
1103                                 break;
1104                         default:
1105                                 break;
1106                         }
1107                         int i;
1108                         str += " data=(";
1109                         for( i = dataBytePos; i<msgdata.length-1; i++ ) {
1110                                 str += String.format( " %02X", msgdata[i] );
1111                         }
1112                         if( i < msgdata.length && (int)(msgdata[i] & 0xFF) != 0xF7 ) {
1113                                 str+=" [ Invalid EOX " + String.format( "%02X", msgdata[i] ) + " ]";
1114                         }
1115                         str += " )";
1116                         return str;
1117                 }
1118                 str += "(";
1119                 for( byte b : msg.getMessage() ) str += String.format( " %02X", b );
1120                 str += " )";
1121                 return str;
1122         }
1123         public static boolean isRhythmPart(int ch) { return (ch == 9); }
1124 }