OSDN Git Service

バグ探索からの指摘を反映
[midichordhelper/MIDIChordHelper.git] / src / camidion / chordhelper / music / MIDISpec.java
index 2894efe..2cddb91 100644 (file)
-package camidion.chordhelper.music;\r
-import java.util.HashMap;\r
-import java.util.Map;\r
-\r
-import javax.sound.midi.InvalidMidiDataException;\r
-import javax.sound.midi.MetaMessage;\r
-import javax.sound.midi.MidiEvent;\r
-import javax.sound.midi.MidiMessage;\r
-import javax.sound.midi.Sequence;\r
-import javax.sound.midi.ShortMessage;\r
-import javax.sound.midi.Track;\r
-/**\r
- * MIDI仕様\r
- */\r
-public class MIDISpec {\r
-       public static final int MAX_CHANNELS = 16;\r
-       public static final int PITCH_BEND_NONE = 8192;\r
-       /**\r
-        * メタメッセージタイプ名マップ\r
-        */\r
-       private static final Map<Integer,String>\r
-               META_MESSAGE_TYPE_NAMES = new HashMap<Integer,String>() {\r
-                       {\r
-                               put(0x00, "Seq Number");\r
-                               put(0x01, "Text");\r
-                               put(0x02, "Copyright");\r
-                               put(0x03, "Seq/Track Name");\r
-                               put(0x04, "Instrument Name");\r
-                               put(0x05, "Lyric");\r
-                               put(0x06, "Marker");\r
-                               put(0x07, "Cue Point");\r
-                               put(0x08, "Program Name");\r
-                               put(0x09, "Device Name");\r
-                               put(0x20, "MIDI Ch.Prefix");\r
-                               put(0x21, "MIDI Output Port");\r
-                               put(0x2F, "End Of Track");\r
-                               put(0x51, "Tempo");\r
-                               put(0x54, "SMPTE Offset");\r
-                               put(0x58, "Time Signature");\r
-                               put(0x59, "Key Signature");\r
-                               put(0x7F, "Sequencer Specific");\r
-                       }\r
-               };\r
-       /**\r
-        * メタメッセージタイプの名前を返します。\r
-        * @param metaMessageType メタメッセージタイプ\r
-        * @return メタメッセージタイプの名前\r
-        */\r
-       public static String getMetaName(int metaMessageType) {\r
-               return META_MESSAGE_TYPE_NAMES.get(metaMessageType);\r
-       }\r
-       /**\r
-        * メタメッセージタイプがテキストのつくものか調べます。\r
-        * @param metaMessageType メタメッセージタイプ\r
-        * @return テキストがつくときtrue\r
-        */\r
-       public static boolean hasMetaText(int metaMessageType) {\r
-               return (metaMessageType > 0 && metaMessageType < 10);\r
-       }\r
-       /**\r
-        * メタメッセージタイプが拍子記号か調べます。\r
-        * @param metaMessageType メタメッセージタイプ\r
-        * @return 拍子記号ならtrue\r
-        */\r
-       public static boolean isTimeSignature(int metaMessageType) {\r
-               return metaMessageType == 0x58;\r
-       }\r
-       /**\r
-        * MIDIメッセージが拍子記号か調べます。\r
-        * @param msg MIDIメッセージ\r
-        * @return 拍子記号ならtrue\r
-        */\r
-       public static boolean isTimeSignature(MidiMessage midiMessage) {\r
-               if ( !(midiMessage instanceof MetaMessage) )\r
-                       return false;\r
-               return isTimeSignature( ((MetaMessage)midiMessage).getType() );\r
-       }\r
-       /**\r
-        * メタメッセージタイプが EOT (End Of Track) か調べます。\r
-        * @param metaMessageType メタメッセージタイプ\r
-        * @return EOTならtrue\r
-        */\r
-       public static boolean isEOT(int metaMessageType) {\r
-               return metaMessageType == 0x2F;\r
-       }\r
-       /**\r
-        * MIDIメッセージが EOT (End Of Track) か調べます。\r
-        * @param midiMessage MIDIメッセージ\r
-        * @return EOTならtrue\r
-        */\r
-       public static boolean isEOT(MidiMessage midiMessage) {\r
-               if ( !(midiMessage instanceof MetaMessage) )\r
-                       return false;\r
-               return isEOT( ((MetaMessage)midiMessage).getType() );\r
-       }\r
-       /**\r
-        * 1分のマイクロ秒数\r
-        */\r
-       public static final int MICROSECOND_PER_MINUTE = (60 * 1000 * 1000);\r
-       /**\r
-        * MIDIのテンポメッセージについているバイト列をQPM単位のテンポに変換します。\r
-        * @param b バイト列\r
-        * @return テンポ[QPM]\r
-        */\r
-       public static int byteArrayToQpmTempo(byte[] b) {\r
-               int tempoInUsPerQuarter\r
-                       = ((b[0] & 0xFF) << 16) | ((b[1] & 0xFF) << 8) | (b[2] & 0xFF);\r
-               return MICROSECOND_PER_MINUTE / tempoInUsPerQuarter;\r
-       }\r
-       /**\r
-        * QPM単位のテンポをMIDIのテンポメッセージ用バイト列に変換します。\r
-        * @param qpm テンポ[QPM]\r
-        * @return MIDIのテンポメッセージ用バイト列\r
-        */\r
-       public static byte[] qpmTempoToByteArray(int qpm) {\r
-               int tempoInUsPerQuarter = MICROSECOND_PER_MINUTE / qpm;\r
-               byte[] b = new byte[3];\r
-               b[0] = (byte)((tempoInUsPerQuarter >> 16) & 0xFF);\r
-               b[1] = (byte)((tempoInUsPerQuarter >> 8) & 0xFF);\r
-               b[2] = (byte)(tempoInUsPerQuarter & 0xFF);\r
-               return b;\r
-       }\r
-       /**\r
-        * トラック名のバイト列を返します。\r
-        * @param track MIDIトラック\r
-        * @return トラック名のバイト列\r
-        */\r
-       public static byte[] getNameBytesOf(Track track) {\r
-               MidiEvent midiEvent;\r
-               MidiMessage message;\r
-               MetaMessage metaMessage;\r
-               for( int i=0; i<track.size(); i++ ) {\r
-                       midiEvent = track.get(i);\r
-                       if( midiEvent.getTick() > 0 ) { // No more event at top, try next track\r
-                               break;\r
-                       }\r
-                       message = midiEvent.getMessage();\r
-                       if( ! (message instanceof MetaMessage) ) { // Not meta message\r
-                               continue;\r
-                       }\r
-                       metaMessage = (MetaMessage)message;\r
-                       if( metaMessage.getType() != 0x03 ) { // Not sequence name\r
-                               continue;\r
-                       }\r
-                       return metaMessage.getData();\r
-               }\r
-               return null;\r
-       }\r
-       /**\r
-        * トラック名のバイト列を設定します。\r
-        * @param track MIDIトラック\r
-        * @param name トラック名\r
-        * @return 成功:true、失敗:false\r
-        */\r
-       public static boolean setNameBytesOf(Track track, byte[] name) {\r
-               MidiEvent midiEvent = null;\r
-               MidiMessage msg = null;\r
-               MetaMessage metaMsg = null;\r
-               for( int i=0; i<track.size(); i++ ) {\r
-                       if(\r
-                               (midiEvent = track.get(i)).getTick() > 0\r
-                               ||\r
-                               (msg = midiEvent.getMessage()) instanceof MetaMessage\r
-                               &&\r
-                               (metaMsg = (MetaMessage)msg).getType() == 0x03\r
-                       ) {\r
-                               break;\r
-                       }\r
-                       metaMsg = null;\r
-               }\r
-               if( metaMsg == null ) {\r
-                       if( name.length == 0 ) return false;\r
-                       track.add(new MidiEvent(\r
-                               (MidiMessage)(metaMsg = new MetaMessage()), 0\r
-                       ));\r
-               }\r
-               try {\r
-                       metaMsg.setMessage(0x03, name, name.length);\r
-               }\r
-               catch( InvalidMidiDataException e ) {\r
-                       e.printStackTrace();\r
-                       return false;\r
-               }\r
-               return true;\r
-       }\r
-       /**\r
-        * シーケンス名のバイト列を返します。\r
-        * <p>トラック名の入った最初のトラックにあるトラック名を\r
-        * シーケンス名として返します。\r
-        * </p>\r
-        * @param seq MIDIシーケンス\r
-        * @return シーケンス名のバイト列\r
-        */\r
-       public static byte[] getNameBytesOf(Sequence seq) {\r
-               // Returns name of the MIDI sequence.\r
-               // A sequence name is placed at top of first track of the sequence.\r
-               //\r
-               Track tracks[] = seq.getTracks();\r
-               byte b[];\r
-               for( Track track : tracks )\r
-                       if( (b = getNameBytesOf(track)) != null ) return b;\r
-               return null;\r
-       }\r
-       /**\r
-        * シーケンス名のバイト列を設定します。\r
-        * <p>先頭のトラックに設定されます。\r
-        * 設定に失敗した場合、順に次のトラックへの設定を試みます。\r
-        * </p>\r
-        *\r
-        * @param seq MIDIシーケンス\r
-        * @param name シーケンス名のバイト列\r
-        * @return 成功:true、失敗:false\r
-        */\r
-       public static boolean setNameBytesOf(Sequence seq, byte[] name) {\r
-               Track tracks[] = seq.getTracks();\r
-               for( Track track : tracks )\r
-                       if( setNameBytesOf(track,name) ) return true;\r
-               return false;\r
-       }\r
-       ///////////////////////////////////////////////////////////////////\r
-       //\r
-       // Channel Message / System Message\r
-       //\r
-       /**\r
-        * MIDIステータス名を返します。\r
-        * @param status MIDIステータス\r
-        * @return MIDIステータス名\r
-        */\r
-       public static String getStatusName( int status ) {\r
-               if( status < 0x80 ) {\r
-                       // No such status\r
-                       return null;\r
-               }\r
-               else if ( status < 0xF0 ) {\r
-                       // Channel Message\r
-                       return ch_msg_status_names[ (status >> 4) - 0x08 ];\r
-               }\r
-               else if ( status <= 0xFF ) {\r
-                       // System Message\r
-                       return sys_msg_names[ status - 0xF0 ];\r
-               }\r
-               return null;\r
-       }\r
-       /**\r
-        * 指定のMIDIショートメッセージがチャンネルメッセージかどうか調べます。\r
-        * @param msg MIDIメッセージ\r
-        * @return MIDIチャンネルメッセージの場合true\r
-        */\r
-       public static boolean isChannelMessage( ShortMessage msg ) {\r
-               return isChannelMessage( msg.getStatus() );\r
-       }\r
-       /**\r
-        * MIDIステータスがチャンネルメッセージかどうか調べます。\r
-        * @param status MIDIステータス\r
-        * @return MIDIチャンネルメッセージの場合true\r
-        */\r
-       public static boolean isChannelMessage( int status ) {\r
-               return ( status < 0xF0 && status >= 0x80 );\r
-       }\r
-       private static final String ch_msg_status_names[] = {\r
-               // 0x80 - 0xE0 : Channel Voice Message\r
-               // 0xB0 : Channel Mode Message\r
-               "NoteOFF", "NoteON",\r
-               "Polyphonic Key Pressure", "Ctrl/Mode",\r
-               "Program", "Ch.Pressure", "Pitch Bend"\r
-       };\r
-       private static final String sys_msg_names[] = {\r
-               // 0xF0 : System Exclusive\r
-               "SysEx",\r
-               //\r
-               // 0xF1 - 0xF7 : System Common Message\r
-               "MIDI Time Code Quarter Frame",\r
-               "Song Position Pointer", "Song Select",\r
-               null, null, "Tune Request", "Special SysEx",\r
-               //\r
-               // 0xF8 - 0xFF : System Realtime Message\r
-               // 0xFF : Meta Message (SMF only, Not for wired MIDI message)\r
-               "Timing Clock", null, "Start", "Continue",\r
-               "Stop", null, "Active Sensing", "Meta / Sys.Reset",\r
-       };\r
-       ///////////////////////////////////////////////////////////////////\r
-       //\r
-       // Control Change / Channel Mode Message\r
-       //\r
-       public static String getControllerName( int controller_number ) {\r
-               if( controller_number < 0x00 ) {\r
-                       return null;\r
-               }\r
-               else if( controller_number < 0x20 ) {\r
-                       String s = controller_names_0[controller_number];\r
-                       if( s != null ) s += " (MSB)";\r
-                       return s;\r
-               }\r
-               else if( controller_number < 0x40 ) {\r
-                       String s = controller_names_0[controller_number - 0x20];\r
-                       if( s != null ) s += " (LSB)";\r
-                       return s;\r
-               }\r
-               else if( controller_number < 0x78 ) {\r
-                       return controller_momentary_switch_names[controller_number - 0x40];\r
-               }\r
-               else if( controller_number < 0x80 ) {\r
-                       return controller_mode_message_names[controller_number - 0x78];\r
-               }\r
-               else {\r
-                       return null;\r
-               }\r
-       }\r
-       private static final String controller_names_0[] = {\r
-               //\r
-               // 0x00-0x0F (MSB)\r
-               "Bank Select", "Modulation Depth", "Breath Controller", null,\r
-               "Foot Controller", "Portamento Time", "Data Entry", "Volume",\r
-               "Balance", null, "Pan", "Expression",\r
-               "Effect Control 1", "Effect Control 2", null, null,\r
-               //\r
-               // 0x10-0x1F (MSB)\r
-               "General Purpose 1", "General Purpose 2",\r
-               "General Purpose 3", "General Purpose 4",\r
-               null, null, null, null,\r
-               null, null, null, null,\r
-               null, null, null, null,\r
-               //\r
-               // 0x20-0x3F (LSB)\r
-       };\r
-       private static final String controller_momentary_switch_names[] = {\r
-               //\r
-               // 0x40-0x4F\r
-               "Damper Pedal (Sustain)", "Portamento",\r
-               "Sustenuto", "Soft Pedal",\r
-               "Legato Footswitch", "Hold 2",\r
-               "Sound Controller 1 (Sound Variation)",\r
-               "Sound Controller 2 (Timbre/Harmonic Intens)",\r
-               "Sound Controller 3 (Release Time)",\r
-               "Sound Controller 4 (Attack Time)",\r
-               "Sound Controller 5 (Brightness)",\r
-               "Sound Controller 6 (Decay Time)",\r
-               "Sound Controller 7 (Vibrato Rate)",\r
-               "Sound Controller 8 (Vibrato Depth)",\r
-               "Sound Controller 9 (Vibrato Delay)",\r
-               "Sound Controller 10 (Undefined)",\r
-               //\r
-               // 0x50-0x5F\r
-               "General Purpose 5", "General Purpose 6 (Temp Change)",\r
-               "General Purpose 7", "General Purpose 8",\r
-               "Portamento Control", null, null, null,\r
-               null, null, null, "Reverb (Ext.Effects Depth)",\r
-               "Tremelo Depth", "Chorus Depth",\r
-               "Celeste (Detune) Depth", "Phaser Depth",\r
-               //\r
-               // 0x60-0x6F\r
-               "Data Increment", "Data Decrement",\r
-               "NRPN (LSB)", "NRPN (MSB)",\r
-               "RPN (LSB)", "RPN (MSB)", null, null,\r
-               null, null, null, null,\r
-               null, null, null, null,\r
-               //\r
-               // 0x70-0x77\r
-               null, null, null, null,\r
-               null, null, null, null\r
-       };\r
-       private static final String controller_mode_message_names[] = {\r
-               // 0x78-0x7F\r
-               "All Sound OFF", "Reset All Controllers",\r
-               "Local Control", "All Notes OFF",\r
-               "Omni Mode OFF", "Omni Mode ON",\r
-               "Mono Mode ON", "Poly Mode ON"\r
-       };\r
-       ///////////////////////////////////////////////////////////////////\r
-       //\r
-       // System Exclusive\r
-       //\r
-       /**\r
-        * システムエクスクルーシブの製造者IDをキーにして製造者名を返すマップ\r
-        */\r
-       public static final Map<Integer,String>\r
-               SYSEX_MANUFACTURER_NAMES = new HashMap<Integer,String>() {\r
-                       {\r
-                               put(0x40,"KAWAI");\r
-                               put(0x41,"Roland");\r
-                               put(0x42,"KORG");\r
-                               put(0x43,"YAMAHA");\r
-                               put(0x44,"CASIO");\r
-                               put(0x7D,"Non-Commercial");\r
-                               put(0x7E,"Universal: Non-RealTime");\r
-                               put(0x7F,"Universal: RealTime");\r
-                       }\r
-               };\r
-       /**\r
-        * MIDIノート番号の最大値\r
-        */\r
-       public static final int MAX_NOTE_NO = 127;\r
-       /**\r
-        * General MIDI の楽器ファミリー名の配列\r
-        */\r
-       public static final String instrument_family_names[] = {\r
-\r
-               "Piano",\r
-               "Chrom.Percussion",\r
-               "Organ",\r
-               "Guitar",\r
-               "Bass",\r
-               "Strings",\r
-               "Ensemble",\r
-               "Brass",\r
-\r
-               "Reed",\r
-               "Pipe",\r
-               "Synth Lead",\r
-               "Synth Pad",\r
-               "Synth Effects",\r
-               "Ethnic",\r
-               "Percussive",\r
-               "Sound Effects",\r
-       };\r
-       /**\r
-        * General MIDI の楽器名(プログラムチェンジのプログラム名)の配列\r
-        */\r
-       public static final String instrument_names[] = {\r
-               "Acoustic Grand Piano",\r
-               "Bright Acoustic Piano",\r
-               "Electric Grand Piano",\r
-               "Honky-tonk Piano",\r
-               "Electric Piano 1",\r
-               "Electric Piano 2",\r
-               "Harpsichord",\r
-               "Clavi",\r
-               "Celesta",\r
-               "Glockenspiel",\r
-               "Music Box",\r
-               "Vibraphone",\r
-               "Marimba",\r
-               "Xylophone",\r
-               "Tubular Bells",\r
-               "Dulcimer",\r
-               "Drawbar Organ",\r
-               "Percussive Organ",\r
-               "Rock Organ",\r
-               "Church Organ",\r
-               "Reed Organ",\r
-               "Accordion",\r
-               "Harmonica",\r
-               "Tango Accordion",\r
-               "Acoustic Guitar (nylon)",\r
-               "Acoustic Guitar (steel)",\r
-               "Electric Guitar (jazz)",\r
-               "Electric Guitar (clean)",\r
-               "Electric Guitar (muted)",\r
-               "Overdriven Guitar",\r
-               "Distortion Guitar",\r
-               "Guitar harmonics",\r
-               "Acoustic Bass",\r
-               "Electric Bass (finger)",\r
-               "Electric Bass (pick)",\r
-               "Fretless Bass",\r
-               "Slap Bass 1",\r
-               "Slap Bass 2",\r
-               "Synth Bass 1",\r
-               "Synth Bass 2",\r
-               "Violin",\r
-               "Viola",\r
-               "Cello",\r
-               "Contrabass",\r
-               "Tremolo Strings",\r
-               "Pizzicato Strings",\r
-               "Orchestral Harp",\r
-               "Timpani",\r
-               "String Ensemble 1",\r
-               "String Ensemble 2",\r
-               "SynthStrings 1",\r
-               "SynthStrings 2",\r
-               "Choir Aahs",\r
-               "Voice Oohs",\r
-               "Synth Voice",\r
-               "Orchestra Hit",\r
-               "Trumpet",\r
-               "Trombone",\r
-               "Tuba",\r
-               "Muted Trumpet",\r
-               "French Horn",\r
-               "Brass Section",\r
-               "SynthBrass 1",\r
-               "SynthBrass 2",\r
-               "Soprano Sax",\r
-               "Alto Sax",\r
-               "Tenor Sax",\r
-               "Baritone Sax",\r
-               "Oboe",\r
-               "English Horn",\r
-               "Bassoon",\r
-               "Clarinet",\r
-               "Piccolo",\r
-               "Flute",\r
-               "Recorder",\r
-               "Pan Flute",\r
-               "Blown Bottle",\r
-               "Shakuhachi",\r
-               "Whistle",\r
-               "Ocarina",\r
-               "Lead 1 (square)",\r
-               "Lead 2 (sawtooth)",\r
-               "Lead 3 (calliope)",\r
-               "Lead 4 (chiff)",\r
-               "Lead 5 (charang)",\r
-               "Lead 6 (voice)",\r
-               "Lead 7 (fifths)",\r
-               "Lead 8 (bass + lead)",\r
-               "Pad 1 (new age)",\r
-               "Pad 2 (warm)",\r
-               "Pad 3 (polysynth)",\r
-               "Pad 4 (choir)",\r
-               "Pad 5 (bowed)",\r
-               "Pad 6 (metallic)",\r
-               "Pad 7 (halo)",\r
-               "Pad 8 (sweep)",\r
-               "FX 1 (rain)",\r
-               "FX 2 (soundtrack)",\r
-               "FX 3 (crystal)",\r
-               "FX 4 (atmosphere)",\r
-               "FX 5 (brightness)",\r
-               "FX 6 (goblins)",\r
-               "FX 7 (echoes)",\r
-               "FX 8 (sci-fi)",\r
-               "Sitar",\r
-               "Banjo",\r
-               "Shamisen",\r
-               "Koto",\r
-               "Kalimba",\r
-               "Bag pipe",\r
-               "Fiddle",\r
-               "Shanai",\r
-               "Tinkle Bell",\r
-               "Agogo",\r
-               "Steel Drums",\r
-               "Woodblock",\r
-               "Taiko Drum",\r
-               "Melodic Tom",\r
-               "Synth Drum",\r
-               "Reverse Cymbal",\r
-               "Guitar Fret Noise",\r
-               "Breath Noise",\r
-               "Seashore",\r
-               "Bird Tweet",\r
-               "Telephone Ring",\r
-               "Helicopter",\r
-               "Applause",\r
-               "Gunshot",\r
-       };\r
-       /**\r
-        * パーカッション用MIDIノート番号の最小値\r
-        */\r
-       public static final int MIN_PERCUSSION_NUMBER = 35;\r
-       /**\r
-        * パーカッション用のMIDIチャンネル(通常はCH.10)における\r
-        * ノート番号からパーカッション名を返します。\r
-        *\r
-        * @param note_no ノート番号\r
-        * @return パーカッション名\r
-        */\r
-       public static String getPercussionName(int note_no) {\r
-               int i = note_no - MIN_PERCUSSION_NUMBER ;\r
-               return i>=0 && i < PERCUSSION_NAMES.length ? PERCUSSION_NAMES[i] : "(Unknown)" ;\r
-       }\r
-       public static final String      PERCUSSION_NAMES[] = {\r
-               "Acoustic Bass Drum",\r
-               "Bass Drum 1",\r
-               "Side Stick",\r
-               "Acoustic Snare",\r
-               "Hand Clap",\r
-               "Electric Snare",\r
-               "Low Floor Tom",\r
-               "Closed Hi Hat",\r
-               "High Floor Tom",\r
-               "Pedal Hi-Hat",\r
-               "Low Tom",\r
-               "Open Hi-Hat",\r
-               "Low-Mid Tom",\r
-               "Hi Mid Tom",\r
-               "Crash Cymbal 1",\r
-               "High Tom",\r
-               "Ride Cymbal 1",\r
-               "Chinese Cymbal",\r
-               "Ride Bell",\r
-               "Tambourine",\r
-               "Splash Cymbal",\r
-               "Cowbell",\r
-               "Crash Cymbal 2",\r
-               "Vibraslap",\r
-               "Ride Cymbal 2",\r
-               "Hi Bongo",\r
-               "Low Bongo",\r
-               "Mute Hi Conga",\r
-               "Open Hi Conga",\r
-               "Low Conga",\r
-               "High Timbale",\r
-               "Low Timbale",\r
-               "High Agogo",\r
-               "Low Agogo",\r
-               "Cabasa",\r
-               "Maracas",\r
-               "Short Whistle",\r
-               "Long Whistle",\r
-               "Short Guiro",\r
-               "Long Guiro",\r
-               "Claves",\r
-               "Hi Wood Block",\r
-               "Low Wood Block",\r
-               "Mute Cuica",\r
-               "Open Cuica",\r
-               "Mute Triangle",\r
-               "Open Triangle",\r
-       };\r
-       public static final String nsx39LyricElements[] = {\r
-               "あ","い","う","え","お",\r
-               "か","き","く","け","こ",\r
-               "が","ぎ","ぐ","げ","ご",\r
-           "きゃ","きゅ","きょ",\r
-           "ぎゃ","ぎゅ","ぎょ",\r
-               "さ","すぃ","す","せ","そ",\r
-               "ざ","ずぃ","ず","ぜ","ぞ",\r
-           "しゃ","し","しゅ","しぇ","しょ",\r
-           "じゃ","じ","じゅ","じぇ","じょ",\r
-               "た","てぃ","とぅ","て","と",\r
-               "だ","でぃ","どぅ","で","ど",\r
-               "てゅ","でゅ",\r
-               "ちゃ","ち","ちゅ","ちぇ","ちょ",\r
-               "つぁ","つぃ","つ","つぇ","つぉ",\r
-               "な","に","ぬ","ね","の",\r
-           "にゃ","にゅ","にょ",\r
-               "は","ひ","ふ","へ","ほ",\r
-               "ば","び","ぶ","べ","ぼ",\r
-               "ぱ","ぴ","ぷ","ぺ","ぽ",\r
-               "ひゃ","ひゅ","ひょ",\r
-               "びゃ","びゅ","びょ",\r
-               "ぴゃ","ぴゅ","ぴょ",\r
-               "ふぁ","ふぃ","ふゅ","ふぇ","ふぉ",\r
-               "ま","み","む","め","も",\r
-               "みゃ","みゅ","みょ",\r
-               "や","ゆ","よ",\r
-               "ら","り","る","れ","ろ",\r
-               "りゃ","りゅ","りょ",\r
-               "わ","うぃ","うぇ","を",\r
-               "ん","ん","ん","ん","ん",\r
-       };\r
-}\r
+package camidion.chordhelper.music;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import javax.sound.midi.InvalidMidiDataException;
+import javax.sound.midi.MetaMessage;
+import javax.sound.midi.MidiEvent;
+import javax.sound.midi.MidiMessage;
+import javax.sound.midi.Sequence;
+import javax.sound.midi.ShortMessage;
+import javax.sound.midi.SysexMessage;
+import javax.sound.midi.Track;
+/**
+ * MIDI仕様(システムエクスクルーシブ含む)
+ */
+public class MIDISpec {
+       public static final int MAX_CHANNELS = 16;
+       public static final int PITCH_BEND_NONE = 8192;
+       /**
+        * メタメッセージタイプ名マップ
+        */
+       private static final Map<Integer,String>
+               META_MESSAGE_TYPE_NAMES = new HashMap<Integer,String>() {
+                       {
+                               put(0x00, "Seq Number");
+                               put(0x01, "Text");
+                               put(0x02, "Copyright");
+                               put(0x03, "Seq/Track Name");
+                               put(0x04, "Instrument Name");
+                               put(0x05, "Lyric");
+                               put(0x06, "Marker");
+                               put(0x07, "Cue Point");
+                               put(0x08, "Program Name");
+                               put(0x09, "Device Name");
+                               put(0x20, "MIDI Ch.Prefix");
+                               put(0x21, "MIDI Output Port");
+                               put(0x2F, "End Of Track");
+                               put(0x51, "Tempo");
+                               put(0x54, "SMPTE Offset");
+                               put(0x58, "Time Signature");
+                               put(0x59, "Key Signature");
+                               put(0x7F, "Sequencer Specific");
+                       }
+               };
+       /**
+        * メタメッセージタイプの名前を返します。
+        * @param metaMessageType メタメッセージタイプ
+        * @return メタメッセージタイプの名前
+        */
+       public static String getMetaName(int metaMessageType) {
+               return META_MESSAGE_TYPE_NAMES.get(metaMessageType);
+       }
+       /**
+        * メタメッセージタイプがテキストのつくものか調べます。
+        * @param metaMessageType メタメッセージタイプ
+        * @return テキストがつくときtrue
+        */
+       public static boolean hasMetaMessageText(int metaMessageType) {
+               return (metaMessageType > 0 && metaMessageType < 10);
+       }
+       /**
+        * メタメッセージタイプが拍子記号か調べます。
+        * @param metaMessageType メタメッセージタイプ
+        * @return 拍子記号ならtrue
+        */
+       public static boolean isTimeSignature(int metaMessageType) {
+               return metaMessageType == 0x58;
+       }
+       /**
+        * MIDIメッセージが拍子記号か調べます。
+        * @param msg MIDIメッセージ
+        * @return 拍子記号ならtrue
+        */
+       public static boolean isTimeSignature(MidiMessage midiMessage) {
+               if ( !(midiMessage instanceof MetaMessage) ) return false;
+               return isTimeSignature( ((MetaMessage)midiMessage).getType() );
+       }
+       /**
+        * メタメッセージタイプが EOT (End Of Track) か調べます。
+        * @param metaMessageType メタメッセージタイプ
+        * @return EOTならtrue
+        */
+       public static boolean isEOT(int metaMessageType) { return metaMessageType == 0x2F; }
+       /**
+        * MIDIメッセージが EOT (End Of Track) か調べます。
+        * @param midiMessage MIDIメッセージ
+        * @return EOTならtrue
+        */
+       public static boolean isEOT(MidiMessage midiMessage) {
+               if ( !(midiMessage instanceof MetaMessage) ) return false;
+               return isEOT( ((MetaMessage)midiMessage).getType() );
+       }
+       /**
+        * 1分のマイクロ秒数
+        */
+       public static final int MICROSECOND_PER_MINUTE = (60 * 1000 * 1000);
+       /**
+        * MIDIのテンポメッセージについているバイト列をQPM単位のテンポに変換します。
+        * @param b バイト列
+        * @return テンポ[QPM]
+        */
+       public static int byteArrayToQpmTempo(byte[] b) {
+               int tempoInUsPerQuarter = ((b[0] & 0xFF) << 16) | ((b[1] & 0xFF) << 8) | (b[2] & 0xFF);
+               return MICROSECOND_PER_MINUTE / tempoInUsPerQuarter;
+       }
+       /**
+        * QPM単位のテンポをMIDIのテンポメッセージ用バイト列に変換します。
+        * @param qpm テンポ[QPM]
+        * @return MIDIのテンポメッセージ用バイト列
+        */
+       public static byte[] qpmTempoToByteArray(int qpm) {
+               int tempoInUsPerQuarter = MICROSECOND_PER_MINUTE / qpm;
+               byte[] b = new byte[3];
+               b[0] = (byte)((tempoInUsPerQuarter >> 16) & 0xFF);
+               b[1] = (byte)((tempoInUsPerQuarter >> 8) & 0xFF);
+               b[2] = (byte)(tempoInUsPerQuarter & 0xFF);
+               return b;
+       }
+       /**
+        * トラック名のバイト列を返します。
+        * @param track MIDIトラック
+        * @return トラック名のバイト列
+        */
+       public static byte[] getNameBytesOf(Track track) {
+               MidiEvent midiEvent;
+               MidiMessage message;
+               MetaMessage metaMessage;
+               for( int i=0; i<track.size(); i++ ) {
+                       midiEvent = track.get(i);
+                       if( midiEvent.getTick() > 0 ) { // No more event at top, try next track
+                               break;
+                       }
+                       message = midiEvent.getMessage();
+                       if( ! (message instanceof MetaMessage) ) { // Not meta message
+                               continue;
+                       }
+                       metaMessage = (MetaMessage)message;
+                       if( metaMessage.getType() != 0x03 ) { // Not sequence name
+                               continue;
+                       }
+                       return metaMessage.getData();
+               }
+               return null;
+       }
+       /**
+        * トラック名のバイト列を設定します。
+        * @param track MIDIトラック
+        * @param name トラック名
+        * @return 成功:true、失敗:false
+        */
+       public static boolean setNameBytesOf(Track track, byte[] name) {
+               MidiEvent midiEvent = null;
+               MidiMessage msg = null;
+               MetaMessage metaMsg = null;
+               for( int i=0; i<track.size(); i++ ) {
+                       if(
+                               (midiEvent = track.get(i)).getTick() > 0
+                               ||
+                               (msg = midiEvent.getMessage()) instanceof MetaMessage
+                               &&
+                               (metaMsg = (MetaMessage)msg).getType() == 0x03
+                       ) {
+                               break;
+                       }
+                       metaMsg = null;
+               }
+               if( metaMsg == null ) {
+                       if( name.length == 0 ) return false;
+                       track.add(new MidiEvent(
+                               (MidiMessage)(metaMsg = new MetaMessage()), 0
+                       ));
+               }
+               try {
+                       metaMsg.setMessage(0x03, name, name.length);
+               }
+               catch( InvalidMidiDataException e ) {
+                       e.printStackTrace();
+                       return false;
+               }
+               return true;
+       }
+       /**
+        * シーケンス名のバイト列を返します。
+        * <p>トラック名の入った最初のトラックにあるトラック名を
+        * シーケンス名として返します。
+        * </p>
+        * @param seq MIDIシーケンス
+        * @return シーケンス名のバイト列
+        */
+       public static byte[] getNameBytesOf(Sequence seq) {
+               // Returns name of the MIDI sequence.
+               // A sequence name is placed at top of first track of the sequence.
+               //
+               Track tracks[] = seq.getTracks();
+               byte b[];
+               for( Track track : tracks ) if( (b = getNameBytesOf(track)) != null ) return b;
+               return null;
+       }
+       /**
+        * シーケンス名のバイト列を設定します。
+        * <p>先頭のトラックに設定されます。
+        * 設定に失敗した場合、順に次のトラックへの設定を試みます。
+        * </p>
+        *
+        * @param seq MIDIシーケンス
+        * @param name シーケンス名のバイト列
+        * @return 成功:true、失敗:false
+        */
+       public static boolean setNameBytesOf(Sequence seq, byte[] name) {
+               Track tracks[] = seq.getTracks();
+               for( Track track : tracks ) if( setNameBytesOf(track,name) ) return true;
+               return false;
+       }
+       /**
+        * シーケンスの名前や歌詞など、メタイベントのテキストをもとに文字コードを判定します。
+        * 判定できなかった場合はnullを返します。
+        * @param seq MIDIシーケンス
+        * @return 文字コード判定結果(またはnull)
+        */
+       public static Charset getCharsetOf(Sequence seq) {
+               Track tracks[] = seq.getTracks();
+               byte[] b = new byte[0];
+               for( Track track : tracks ) {
+                       MidiMessage message;
+                       MetaMessage metaMessage;
+                       for( int i=0; i<track.size(); i++ ) {
+                               message = track.get(i).getMessage();
+                               if( ! (message instanceof MetaMessage) ) continue;
+                               metaMessage = (MetaMessage)message;
+                               if( ! hasMetaMessageText(metaMessage.getType()) ) continue;
+                               byte[] additional = metaMessage.getData();
+                               byte[] concated = new byte[b.length + additional.length];
+                               System.arraycopy(b, 0, concated, 0, b.length);
+                               System.arraycopy(additional, 0, concated, b.length, additional.length);
+                               b = concated;
+                       }
+               }
+               if( b.length > 0 ) {
+                       try {
+                               String autoDetectedName = new String(b, "JISAutoDetect");
+                               Set<Map.Entry<String,Charset>> entrySet;
+                               entrySet = Charset.availableCharsets().entrySet();
+                               for( Map.Entry<String,Charset> entry : entrySet ) {
+                                       Charset cs = entry.getValue();
+                                       if( autoDetectedName.equals(new String(b, cs)) ) return cs;
+                               }
+                       } catch (UnsupportedEncodingException e) {
+                               e.printStackTrace();
+                       }
+               }
+               return null;
+       }
+       ///////////////////////////////////////////////////////////////////
+       //
+       // Channel Message / System Message
+       //
+       /**
+        * MIDIステータス名を返します。
+        * @param status MIDIステータス
+        * @return MIDIステータス名
+        */
+       public static String getStatusName( int status ) {
+               if( status < 0x80 ) {
+                       // No such status
+                       return null;
+               }
+               else if ( status < 0xF0 ) {
+                       // Channel Message
+                       return ch_msg_status_names[ (status >> 4) - 0x08 ];
+               }
+               else if ( status <= 0xFF ) {
+                       // System Message
+                       return sys_msg_names[ status - 0xF0 ];
+               }
+               return null;
+       }
+       /**
+        * 指定のMIDIショートメッセージがチャンネルメッセージかどうか調べます。
+        * @param msg MIDIメッセージ
+        * @return MIDIチャンネルメッセージの場合true
+        */
+       public static boolean isChannelMessage( ShortMessage msg ) {
+               return isChannelMessage( msg.getStatus() );
+       }
+       /**
+        * MIDIステータスがチャンネルメッセージかどうか調べます。
+        * @param status MIDIステータス
+        * @return MIDIチャンネルメッセージの場合true
+        */
+       public static boolean isChannelMessage( int status ) {
+               return ( status < 0xF0 && status >= 0x80 );
+       }
+       private static final String ch_msg_status_names[] = {
+               // 0x80 - 0xE0 : Channel Voice Message
+               // 0xB0 : Channel Mode Message
+               "NoteOFF", "NoteON",
+               "Polyphonic Key Pressure", "Ctrl/Mode",
+               "Program", "Ch.Pressure", "Pitch Bend"
+       };
+       private static final String sys_msg_names[] = {
+               // 0xF0 : System Exclusive
+               "SysEx",
+               //
+               // 0xF1 - 0xF7 : System Common Message
+               "MIDI Time Code Quarter Frame",
+               "Song Position Pointer", "Song Select",
+               null, null, "Tune Request", "Special SysEx",
+               //
+               // 0xF8 - 0xFF : System Realtime Message
+               // 0xFF : Meta Message (SMF only, Not for wired MIDI message)
+               "Timing Clock", null, "Start", "Continue",
+               "Stop", null, "Active Sensing", "Meta / Sys.Reset",
+       };
+       ///////////////////////////////////////////////////////////////////
+       //
+       // Control Change / Channel Mode Message
+       //
+       /**
+        * コントロールチェンジの名前を返します。
+        * @param controllerNumber コントローラ番号
+        * @return コントロールチェンジの名前
+        */
+       public static String getControllerName( int controllerNumber ) {
+               if( controllerNumber < 0x00 ) {
+                       return null;
+               }
+               else if( controllerNumber < 0x20 ) {
+                       String s = controllerNames0[controllerNumber];
+                       if( s != null ) s += " (MSB)";
+                       return s;
+               }
+               else if( controllerNumber < 0x40 ) {
+                       String s = controllerNames0[controllerNumber - 0x20];
+                       if( s != null ) s += " (LSB)";
+                       return s;
+               }
+               else if( controllerNumber < 0x78 ) {
+                       return controllerMomentarySwitchNames[controllerNumber - 0x40];
+               }
+               else if( controllerNumber < 0x80 ) {
+                       return controllerModeMessageNames[controllerNumber - 0x78];
+               }
+               else {
+                       return null;
+               }
+       }
+       private static final String controllerNames0[] = {
+               //
+               // 0x00-0x0F (MSB)
+               "Bank Select", "Modulation Depth", "Breath Controller", null,
+               "Foot Controller", "Portamento Time", "Data Entry", "Volume",
+               "Balance", null, "Pan", "Expression",
+               "Effect Control 1", "Effect Control 2", null, null,
+               //
+               // 0x10-0x1F (MSB)
+               "General Purpose 1", "General Purpose 2",
+               "General Purpose 3", "General Purpose 4",
+               null, null, null, null,
+               null, null, null, null,
+               null, null, null, null,
+               //
+               // 0x20-0x3F (LSB)
+       };
+       private static final String controllerMomentarySwitchNames[] = {
+               //
+               // 0x40-0x4F
+               "Damper Pedal (Sustain)", "Portamento",
+               "Sustenuto", "Soft Pedal",
+               "Legato Footswitch", "Hold 2",
+               "Sound Controller 1 (Sound Variation)",
+               "Sound Controller 2 (Timbre/Harmonic Intens)",
+               "Sound Controller 3 (Release Time)",
+               "Sound Controller 4 (Attack Time)",
+               "Sound Controller 5 (Brightness)",
+               "Sound Controller 6 (Decay Time)",
+               "Sound Controller 7 (Vibrato Rate)",
+               "Sound Controller 8 (Vibrato Depth)",
+               "Sound Controller 9 (Vibrato Delay)",
+               "Sound Controller 10 (Undefined)",
+               //
+               // 0x50-0x5F
+               "General Purpose 5", "General Purpose 6 (Temp Change)",
+               "General Purpose 7", "General Purpose 8",
+               "Portamento Control", null, null, null,
+               null, null, null, "Reverb (Ext.Effects Depth)",
+               "Tremelo Depth", "Chorus Depth",
+               "Celeste (Detune) Depth", "Phaser Depth",
+               //
+               // 0x60-0x6F
+               "Data Increment", "Data Decrement",
+               "NRPN (LSB)", "NRPN (MSB)",
+               "RPN (LSB)", "RPN (MSB)", null, null,
+               null, null, null, null,
+               null, null, null, null,
+               //
+               // 0x70-0x77
+               null, null, null, null,
+               null, null, null, null
+       };
+       private static final String controllerModeMessageNames[] = {
+               // 0x78-0x7F
+               "All Sound OFF", "Reset All Controllers",
+               "Local Control", "All Notes OFF",
+               "Omni Mode OFF", "Omni Mode ON",
+               "Mono Mode ON", "Poly Mode ON"
+       };
+       ///////////////////////////////////////////////////////////////////
+       //
+       // System Exclusive
+       //
+       /**
+        * システムエクスクルーシブの製造者IDをキーにして製造者名を返すマップ
+        */
+       public static final Map<Integer,String>
+               SYSEX_MANUFACTURER_NAMES = new HashMap<Integer,String>() {
+                       {
+                               put(0x40,"KAWAI");
+                               put(0x41,"Roland");
+                               put(0x42,"KORG");
+                               put(0x43,"YAMAHA");
+                               put(0x44,"CASIO");
+                               put(0x7D,"Non-Commercial");
+                               put(0x7E,"Universal: Non-RealTime");
+                               put(0x7F,"Universal: RealTime");
+                       }
+               };
+       /**
+        * MIDIノート番号の最大値
+        */
+       public static final int MAX_NOTE_NO = 127;
+       /**
+        * General MIDI の楽器ファミリー名の配列
+        */
+       public static final String instrumentFamilyNames[] = {
+
+               "Piano",
+               "Chrom.Percussion",
+               "Organ",
+               "Guitar",
+               "Bass",
+               "Strings",
+               "Ensemble",
+               "Brass",
+
+               "Reed",
+               "Pipe",
+               "Synth Lead",
+               "Synth Pad",
+               "Synth Effects",
+               "Ethnic",
+               "Percussive",
+               "Sound Effects",
+       };
+       /**
+        * General MIDI の楽器名(プログラムチェンジのプログラム名)の配列
+        */
+       public static final String instrumentNames[] = {
+               "Acoustic Grand Piano",
+               "Bright Acoustic Piano",
+               "Electric Grand Piano",
+               "Honky-tonk Piano",
+               "Electric Piano 1",
+               "Electric Piano 2",
+               "Harpsichord",
+               "Clavi",
+               "Celesta",
+               "Glockenspiel",
+               "Music Box",
+               "Vibraphone",
+               "Marimba",
+               "Xylophone",
+               "Tubular Bells",
+               "Dulcimer",
+               "Drawbar Organ",
+               "Percussive Organ",
+               "Rock Organ",
+               "Church Organ",
+               "Reed Organ",
+               "Accordion",
+               "Harmonica",
+               "Tango Accordion",
+               "Acoustic Guitar (nylon)",
+               "Acoustic Guitar (steel)",
+               "Electric Guitar (jazz)",
+               "Electric Guitar (clean)",
+               "Electric Guitar (muted)",
+               "Overdriven Guitar",
+               "Distortion Guitar",
+               "Guitar harmonics",
+               "Acoustic Bass",
+               "Electric Bass (finger)",
+               "Electric Bass (pick)",
+               "Fretless Bass",
+               "Slap Bass 1",
+               "Slap Bass 2",
+               "Synth Bass 1",
+               "Synth Bass 2",
+               "Violin",
+               "Viola",
+               "Cello",
+               "Contrabass",
+               "Tremolo Strings",
+               "Pizzicato Strings",
+               "Orchestral Harp",
+               "Timpani",
+               "String Ensemble 1",
+               "String Ensemble 2",
+               "SynthStrings 1",
+               "SynthStrings 2",
+               "Choir Aahs",
+               "Voice Oohs",
+               "Synth Voice",
+               "Orchestra Hit",
+               "Trumpet",
+               "Trombone",
+               "Tuba",
+               "Muted Trumpet",
+               "French Horn",
+               "Brass Section",
+               "SynthBrass 1",
+               "SynthBrass 2",
+               "Soprano Sax",
+               "Alto Sax",
+               "Tenor Sax",
+               "Baritone Sax",
+               "Oboe",
+               "English Horn",
+               "Bassoon",
+               "Clarinet",
+               "Piccolo",
+               "Flute",
+               "Recorder",
+               "Pan Flute",
+               "Blown Bottle",
+               "Shakuhachi",
+               "Whistle",
+               "Ocarina",
+               "Lead 1 (square)",
+               "Lead 2 (sawtooth)",
+               "Lead 3 (calliope)",
+               "Lead 4 (chiff)",
+               "Lead 5 (charang)",
+               "Lead 6 (voice)",
+               "Lead 7 (fifths)",
+               "Lead 8 (bass + lead)",
+               "Pad 1 (new age)",
+               "Pad 2 (warm)",
+               "Pad 3 (polysynth)",
+               "Pad 4 (choir)",
+               "Pad 5 (bowed)",
+               "Pad 6 (metallic)",
+               "Pad 7 (halo)",
+               "Pad 8 (sweep)",
+               "FX 1 (rain)",
+               "FX 2 (soundtrack)",
+               "FX 3 (crystal)",
+               "FX 4 (atmosphere)",
+               "FX 5 (brightness)",
+               "FX 6 (goblins)",
+               "FX 7 (echoes)",
+               "FX 8 (sci-fi)",
+               "Sitar",
+               "Banjo",
+               "Shamisen",
+               "Koto",
+               "Kalimba",
+               "Bag pipe",
+               "Fiddle",
+               "Shanai",
+               "Tinkle Bell",
+               "Agogo",
+               "Steel Drums",
+               "Woodblock",
+               "Taiko Drum",
+               "Melodic Tom",
+               "Synth Drum",
+               "Reverse Cymbal",
+               "Guitar Fret Noise",
+               "Breath Noise",
+               "Seashore",
+               "Bird Tweet",
+               "Telephone Ring",
+               "Helicopter",
+               "Applause",
+               "Gunshot",
+       };
+       /**
+        * パーカッション用MIDIノート番号の最小値
+        */
+       public static final int MIN_PERCUSSION_NUMBER = 35;
+       /**
+        * パーカッション用のMIDIチャンネル(通常はCH.10)における
+        * ノート番号からパーカッション名を返します。
+        *
+        * @param note_no ノート番号
+        * @return パーカッション名
+        */
+       public static String getPercussionName(int note_no) {
+               int i = note_no - MIN_PERCUSSION_NUMBER ;
+               return i>=0 && i < PERCUSSION_NAMES.length ? PERCUSSION_NAMES[i] : "(Unknown)" ;
+       }
+       public static final String      PERCUSSION_NAMES[] = {
+               "Acoustic Bass Drum",
+               "Bass Drum 1",
+               "Side Stick",
+               "Acoustic Snare",
+               "Hand Clap",
+               "Electric Snare",
+               "Low Floor Tom",
+               "Closed Hi Hat",
+               "High Floor Tom",
+               "Pedal Hi-Hat",
+               "Low Tom",
+               "Open Hi-Hat",
+               "Low-Mid Tom",
+               "Hi Mid Tom",
+               "Crash Cymbal 1",
+               "High Tom",
+               "Ride Cymbal 1",
+               "Chinese Cymbal",
+               "Ride Bell",
+               "Tambourine",
+               "Splash Cymbal",
+               "Cowbell",
+               "Crash Cymbal 2",
+               "Vibraslap",
+               "Ride Cymbal 2",
+               "Hi Bongo",
+               "Low Bongo",
+               "Mute Hi Conga",
+               "Open Hi Conga",
+               "Low Conga",
+               "High Timbale",
+               "Low Timbale",
+               "High Agogo",
+               "Low Agogo",
+               "Cabasa",
+               "Maracas",
+               "Short Whistle",
+               "Long Whistle",
+               "Short Guiro",
+               "Long Guiro",
+               "Claves",
+               "Hi Wood Block",
+               "Low Wood Block",
+               "Mute Cuica",
+               "Open Cuica",
+               "Mute Triangle",
+               "Open Triangle",
+       };
+       public static final String nsx39LyricElements[] = {
+               "あ","い","う","え","お",
+               "か","き","く","け","こ",
+               "が","ぎ","ぐ","げ","ご",
+           "きゃ","きゅ","きょ",
+           "ぎゃ","ぎゅ","ぎょ",
+               "さ","すぃ","す","せ","そ",
+               "ざ","ずぃ","ず","ぜ","ぞ",
+           "しゃ","し","しゅ","しぇ","しょ",
+           "じゃ","じ","じゅ","じぇ","じょ",
+               "た","てぃ","とぅ","て","と",
+               "だ","でぃ","どぅ","で","ど",
+               "てゅ","でゅ",
+               "ちゃ","ち","ちゅ","ちぇ","ちょ",
+               "つぁ","つぃ","つ","つぇ","つぉ",
+               "な","に","ぬ","ね","の",
+           "にゃ","にゅ","にょ",
+               "は","ひ","ふ","へ","ほ",
+               "ば","び","ぶ","べ","ぼ",
+               "ぱ","ぴ","ぷ","ぺ","ぽ",
+               "ひゃ","ひゅ","ひょ",
+               "びゃ","びゅ","びょ",
+               "ぴゃ","ぴゅ","ぴょ",
+               "ふぁ","ふぃ","ふゅ","ふぇ","ふぉ",
+               "ま","み","む","め","も",
+               "みゃ","みゅ","みょ",
+               "や","ゆ","よ",
+               "ら","り","る","れ","ろ",
+               "りゃ","りゅ","りょ",
+               "わ","うぃ","うぇ","を",
+               "ん","ん","ん","ん","ん",
+       };
+       /**
+        * MIDIメッセージの内容を文字列で返します。
+        * @param msg MIDIメッセージ
+        * @param charset MIDIメタメッセージに含まれるテキストデータの文字コード
+        * @return MIDIメッセージの内容を表す文字列
+        */
+       public static String msgToString(MidiMessage msg, Charset charset) {
+               String str = "";
+               if( msg instanceof ShortMessage ) {
+                       ShortMessage shortmsg = (ShortMessage)msg;
+                       int status = msg.getStatus();
+                       String statusName = getStatusName(status);
+                       int data1 = shortmsg.getData1();
+                       int data2 = shortmsg.getData2();
+                       if( isChannelMessage(status) ) {
+                               int channel = shortmsg.getChannel();
+                               String channelPrefix = "Ch."+(channel+1) + ": ";
+                               String statusPrefix = (
+                                       statusName == null ? String.format("status=0x%02X",status) : statusName
+                               ) + ": ";
+                               int cmd = shortmsg.getCommand();
+                               switch( cmd ) {
+                               case ShortMessage.NOTE_OFF:
+                               case ShortMessage.NOTE_ON:
+                                       str += channelPrefix + statusPrefix + data1;
+                                       str += ":[";
+                                       if( MIDISpec.isRhythmPart(channel) ) {
+                                               str += getPercussionName(data1);
+                                       }
+                                       else {
+                                               str += NoteSymbol.noteNoToSymbol(data1);
+                                       }
+                                       str +="] Velocity=" + data2;
+                                       break;
+                               case ShortMessage.POLY_PRESSURE:
+                                       str += channelPrefix + statusPrefix + "Note=" + data1 + " Pressure=" + data2;
+                                       break;
+                               case ShortMessage.PROGRAM_CHANGE:
+                                       str += channelPrefix + statusPrefix + data1 + ":[" + instrumentNames[data1] + "]";
+                                       if( data2 != 0 ) str += " data2=" + data2;
+                                       break;
+                               case ShortMessage.CHANNEL_PRESSURE:
+                                       str += channelPrefix + statusPrefix + data1;
+                                       if( data2 != 0 ) str += " data2=" + data2;
+                                       break;
+                               case ShortMessage.PITCH_BEND:
+                               {
+                                       int val = ((data1 & 0x7F) | ((data2 & 0x7F) << 7));
+                                       str += channelPrefix + statusPrefix + ( (val-8192) * 100 / 8191) + "% (" + val + ")";
+                               }
+                               break;
+                               case ShortMessage.CONTROL_CHANGE:
+                               {
+                                       // Control / Mode message name
+                                       String ctrl_name = getControllerName(data1);
+                                       str += channelPrefix + (data1 < 0x78 ? "CtrlChg: " : "ModeMsg: ");
+                                       if( ctrl_name == null ) {
+                                               str += " No.=" + data1 + " Value=" + data2;
+                                               return str;
+                                       }
+                                       str += ctrl_name;
+                                       //
+                                       // Controller's value
+                                       switch( data1 ) {
+                                       case 0x40: case 0x41: case 0x42: case 0x43: case 0x45:
+                                               str += " " + ( data2==0x3F?"OFF":data2==0x40?"ON":data2 );
+                                               break;
+                                       case 0x44: // Legato Footswitch
+                                               str += " " + ( data2==0x3F?"Normal":data2==0x40?"Legato":data2 );
+                                               break;
+                                       case 0x7A: // Local Control
+                                               str += " " + ( data2==0x00?"OFF":data2==0x7F?"ON":data2 );
+                                               break;
+                                       default:
+                                               str += " " + data2;
+                                               break;
+                                       }
+                               }
+                               break;
+
+                               default:
+                                       // Never reached here
+                                       break;
+                               }
+                       }
+                       else { // System Message
+                               str += (statusName == null ? ("status="+status) : statusName );
+                               str += " (" + data1 + "," + data2 + ")";
+                       }
+                       return str;
+               }
+               else if( msg instanceof MetaMessage ) {
+                       MetaMessage metamsg = (MetaMessage)msg;
+                       byte[] msgdata = metamsg.getData();
+                       int msgtype = metamsg.getType();
+                       str += "Meta: ";
+                       String meta_name = getMetaName(msgtype);
+                       if( meta_name == null ) {
+                               str += "Unknown MessageType="+msgtype + " Values=(";
+                               for( byte b : msgdata ) str += String.format( " %02X", b );
+                               str += " )";
+                               return str;
+                       }
+                       // Add the message type name
+                       str += meta_name;
+                       //
+                       // Add the text data
+                       if( hasMetaMessageText(msgtype) ) {
+                               str +=" ["+(new String(msgdata,charset))+"]";
+                               return str;
+                       }
+                       // Add the numeric data
+                       switch(msgtype) {
+                       case 0x00: // Sequence Number (for MIDI Format 2)
+                               if( msgdata.length == 2 ) {
+                                       str += String.format(
+                                               ": %04X",
+                                               ((msgdata[0] & 0xFF) << 8) | (msgdata[1] & 0xFF)
+                                       );
+                                       break;
+                               }
+                               str += ": Size not 2 byte : data=(";
+                               for( byte b : msgdata ) str += String.format( " %02X", b );
+                               str += " )";
+                               break;
+                       case 0x20: // MIDI Ch.Prefix
+                       case 0x21: // MIDI Output Port
+                               if( msgdata.length == 1 ) {
+                                       str += String.format( ": %02X", msgdata[0] & 0xFF );
+                                       break;
+                               }
+                               str += ": Size not 1 byte : data=(";
+                               for( byte b : msgdata ) str += String.format( " %02X", b );
+                               str += " )";
+                               break;
+                       case 0x51: // Tempo
+                               str += ": " + byteArrayToQpmTempo( msgdata ) + "[QPM] (";
+                               for( byte b : msgdata ) str += String.format( " %02X", b );
+                               str += " )";
+                               break;
+                       case 0x54: // SMPTE Offset
+                               if( msgdata.length == 5 ) {
+                                       str += ": "
+                                               + (msgdata[0] & 0xFF) + ":"
+                                               + (msgdata[1] & 0xFF) + ":"
+                                               + (msgdata[2] & 0xFF) + "."
+                                               + (msgdata[3] & 0xFF) + "."
+                                               + (msgdata[4] & 0xFF);
+                                       break;
+                               }
+                               str += ": Size not 5 byte : data=(";
+                               for( byte b : msgdata ) str += String.format( " %02X", b );
+                               str += " )";
+                               break;
+                       case 0x58: // Time Signature
+                               if( msgdata.length == 4 ) {
+                                       str +=": " + msgdata[0] + "/" + (1 << msgdata[1]);
+                                       str +=", "+msgdata[2]+"[clk/beat], "+msgdata[3]+"[32nds/24clk]";
+                                       break;
+                               }
+                               str += ": Size not 4 byte : data=(";
+                               for( byte b : msgdata ) str += String.format( " %02X", b );
+                               str += " )";
+                               break;
+                       case 0x59: // Key Signature
+                               if( msgdata.length == 2 ) {
+                                       Key key = new Key(msgdata);
+                                       str += ": " + key.signatureDescription();
+                                       str += " (" + key.toStringIn(SymbolLanguage.NAME) + ")";
+                                       break;
+                               }
+                               str += ": Size not 2 byte : data=(";
+                               for( byte b : msgdata ) str += String.format( " %02X", b );
+                               str += " )";
+                               break;
+                       case 0x7F: // Sequencer Specific Meta Event
+                               str += " (";
+                               for( byte b : msgdata ) str += String.format( " %02X", b );
+                               str += " )";
+                               break;
+                       }
+                       return str;
+               }
+               else if( msg instanceof SysexMessage ) {
+                       SysexMessage sysexmsg = (SysexMessage)msg;
+                       int status = sysexmsg.getStatus();
+                       byte[] msgdata = sysexmsg.getData();
+                       int dataBytePos = 1;
+                       switch( status ) {
+                       case SysexMessage.SYSTEM_EXCLUSIVE:
+                               str += "SysEx: ";
+                               break;
+                       case SysexMessage.SPECIAL_SYSTEM_EXCLUSIVE:
+                               str += "SysEx(Special): ";
+                               break;
+                       default:
+                               str += "SysEx: Invalid (status="+status+") ";
+                               break;
+                       }
+                       if( msgdata.length < 1 ) {
+                               str += " Invalid data size: " + msgdata.length;
+                               return str;
+                       }
+                       int manufacturerId = (int)(msgdata[0] & 0xFF);
+                       int deviceId = (int)(msgdata[1] & 0xFF);
+                       int modelId = (int)(msgdata[2] & 0xFF);
+                       String manufacturerName = SYSEX_MANUFACTURER_NAMES.get(manufacturerId);
+                       if( manufacturerName == null ) {
+                               manufacturerName = String.format("[Manufacturer code %02X]", msgdata[0]);
+                       }
+                       str += manufacturerName + String.format(" (DevID=0x%02X)", deviceId);
+                       switch( manufacturerId ) {
+                       case 0x7E: // Non-Realtime Universal
+                               dataBytePos++;
+                               int sub_id_1 = modelId;
+                               int sub_id_2 = (int)(msgdata[3] & 0xFF);
+                               switch( sub_id_1 ) {
+                               case 0x09: // General MIDI (GM)
+                                       switch( sub_id_2 ) {
+                                       case 0x01: str += " GM System ON"; return str;
+                                       case 0x02: str += " GM System OFF"; return str;
+                                       }
+                                       break;
+                               default:
+                                       break;
+                               }
+                               break;
+                               // case 0x7F: // Realtime Universal
+                       case 0x41: // Roland
+                               dataBytePos++;
+                               switch( modelId ) {
+                               case 0x42:
+                                       str += " [GS]"; dataBytePos++;
+                                       if( msgdata[3]==0x12 ) {
+                                               str += "DT1:"; dataBytePos++;
+                                               switch( msgdata[4] ) {
+                                               case 0x00:
+                                                       if( msgdata[5]==0x00 ) {
+                                                               if( msgdata[6]==0x7F ) {
+                                                                       if( msgdata[7]==0x00 ) {
+                                                                               str += " [88] System Mode Set (Mode 1: Single Module)"; return str;
+                                                                       }
+                                                                       else if( msgdata[7]==0x01 ) {
+                                                                               str += " [88] System Mode Set (Mode 2: Double Module)"; return str;
+                                                                       }
+                                                               }
+                                                       }
+                                                       else if( msgdata[5]==0x01 ) {
+                                                               int port = (msgdata[7] & 0xFF);
+                                                               str += String.format(
+                                                                               " [88] Ch.Msg Rx Port: Block=0x%02X, Port=%s",
+                                                                               msgdata[6],
+                                                                               port==0?"A":port==1?"B":String.format("0x%02X",port)
+                                                                               );
+                                                               return str;
+                                                       }
+                                                       break;
+                                               case 0x40:
+                                                       if( msgdata[5]==0x00 ) {
+                                                               switch( msgdata[6] ) {
+                                                               case 0x00: str += " Master Tune: "; dataBytePos += 3; break;
+                                                               case 0x04: str += " Master Volume: "; dataBytePos += 3; break;
+                                                               case 0x05: str += " Master Key Shift: "; dataBytePos += 3; break;
+                                                               case 0x06: str += " Master Pan: "; dataBytePos += 3; break;
+                                                               case 0x7F:
+                                                                       switch( msgdata[7] ) {
+                                                                       case 0x00: str += " GS Reset"; return str;
+                                                                       case 0x7F: str += " Exit GS Mode"; return str;
+                                                                       }
+                                                                       break;
+                                                               }
+                                                       }
+                                                       else if( msgdata[5]==0x01 ) {
+                                                               switch( msgdata[6] ) {
+                                                               // case 0x00: str += ""; break;
+                                                               // case 0x10: str += ""; break;
+                                                               case 0x30: str += " Reverb Macro: "; dataBytePos += 3; break;
+                                                               case 0x31: str += " Reverb Character: "; dataBytePos += 3; break;
+                                                               case 0x32: str += " Reverb Pre-LPF: "; dataBytePos += 3; break;
+                                                               case 0x33: str += " Reverb Level: "; dataBytePos += 3; break;
+                                                               case 0x34: str += " Reverb Time: "; dataBytePos += 3; break;
+                                                               case 0x35: str += " Reverb Delay FB: "; dataBytePos += 3; break;
+                                                               case 0x36: str += " Reverb Chorus Level: "; dataBytePos += 3; break;
+                                                               case 0x37: str += " [88] Reverb Predelay Time: "; dataBytePos += 3; break;
+                                                               case 0x38: str += " Chorus Macro: "; dataBytePos += 3; break;
+                                                               case 0x39: str += " Chorus Pre-LPF: "; dataBytePos += 3; break;
+                                                               case 0x3A: str += " Chorus Level: "; dataBytePos += 3; break;
+                                                               case 0x3B: str += " Chorus FB: "; dataBytePos += 3; break;
+                                                               case 0x3C: str += " Chorus Delay: "; dataBytePos += 3; break;
+                                                               case 0x3D: str += " Chorus Rate: "; dataBytePos += 3; break;
+                                                               case 0x3E: str += " Chorus Depth: "; dataBytePos += 3; break;
+                                                               case 0x3F: str += " Chorus Send Level To Reverb: "; dataBytePos += 3; break;
+                                                               case 0x40: str += " [88] Chorus Send Level To Delay: "; dataBytePos += 3; break;
+                                                               case 0x50: str += " [88] Delay Macro: "; dataBytePos += 3; break;
+                                                               case 0x51: str += " [88] Delay Pre-LPF: "; dataBytePos += 3; break;
+                                                               case 0x52: str += " [88] Delay Time Center: "; dataBytePos += 3; break;
+                                                               case 0x53: str += " [88] Delay Time Ratio Left: "; dataBytePos += 3; break;
+                                                               case 0x54: str += " [88] Delay Time Ratio Right: "; dataBytePos += 3; break;
+                                                               case 0x55: str += " [88] Delay Level Center: "; dataBytePos += 3; break;
+                                                               case 0x56: str += " [88] Delay Level Left: "; dataBytePos += 3; break;
+                                                               case 0x57: str += " [88] Delay Level Right: "; dataBytePos += 3; break;
+                                                               case 0x58: str += " [88] Delay Level: "; dataBytePos += 3; break;
+                                                               case 0x59: str += " [88] Delay FB: "; dataBytePos += 3; break;
+                                                               case 0x5A: str += " [88] Delay Send Level To Reverb: "; dataBytePos += 3; break;
+                                                               }
+                                                       }
+                                                       else if( msgdata[5]==0x02 ) {
+                                                               switch( msgdata[6] ) {
+                                                               case 0x00: str += " [88] EQ Low Freq: "; dataBytePos += 3; break;
+                                                               case 0x01: str += " [88] EQ Low Gain: "; dataBytePos += 3; break;
+                                                               case 0x02: str += " [88] EQ High Freq: "; dataBytePos += 3; break;
+                                                               case 0x03: str += " [88] EQ High Gain: "; dataBytePos += 3; break;
+                                                               }
+                                                       }
+                                                       else if( msgdata[5]==0x03 ) {
+                                                               if( msgdata[6] == 0x00 ) {
+                                                                       str += " [Pro] EFX Type: "; dataBytePos += 3;
+                                                               }
+                                                               else if( msgdata[6] >= 0x03 && msgdata[6] <= 0x16 ) {
+                                                                       str += String.format(" [Pro] EFX Param %d", msgdata[6]-2 );
+                                                                       dataBytePos += 3;
+                                                               }
+                                                               else if( msgdata[6] == 0x17 ) {
+                                                                       str += " [Pro] EFX Send Level To Reverb: "; dataBytePos += 3;
+                                                               }
+                                                               else if( msgdata[6] == 0x18 ) {
+                                                                       str += " [Pro] EFX Send Level To Chorus: "; dataBytePos += 3;
+                                                               }
+                                                               else if( msgdata[6] == 0x19 ) {
+                                                                       str += " [Pro] EFX Send Level To Delay: "; dataBytePos += 3;
+                                                               }
+                                                               else if( msgdata[6] == 0x1B ) {
+                                                                       str += " [Pro] EFX Ctrl Src1: "; dataBytePos += 3;
+                                                               }
+                                                               else if( msgdata[6] == 0x1C ) {
+                                                                       str += " [Pro] EFX Ctrl Depth1: "; dataBytePos += 3;
+                                                               }
+                                                               else if( msgdata[6] == 0x1D ) {
+                                                                       str += " [Pro] EFX Ctrl Src2: "; dataBytePos += 3;
+                                                               }
+                                                               else if( msgdata[6] == 0x1E ) {
+                                                                       str += " [Pro] EFX Ctrl Depth2: "; dataBytePos += 3;
+                                                               }
+                                                               else if( msgdata[6] == 0x1F ) {
+                                                                       str += " [Pro] EFX Send EQ Switch: "; dataBytePos += 3;
+                                                               }
+                                                       }
+                                                       else if( (msgdata[5] & 0xF0) == 0x10 ) {
+                                                               int ch = (msgdata[5] & 0x0F);
+                                                               if( ch <= 9 ) ch--; else if( ch == 0 ) ch = 9;
+                                                               if( msgdata[6]==0x02 ) {
+                                                                       str += String.format(
+                                                                                       " Rx Ch: Part=%d(0x%02X) Ch=0x%02X", (ch+1),  msgdata[5], msgdata[7]
+                                                                                       );
+                                                                       return str;
+                                                               }
+                                                               else if( msgdata[6]==0x15 ) {
+                                                                       String map;
+                                                                       switch( msgdata[7] ) {
+                                                                       case 0: map = " NormalPart"; break;
+                                                                       case 1: map = " DrumMap1"; break;
+                                                                       case 2: map = " DrumMap2"; break;
+                                                                       default: map = String.format("0x%02X",msgdata[7]); break;
+                                                                       }
+                                                                       str += String.format(
+                                                                               " Rhythm Part: Ch=%d(0x%02X) Map=%s",
+                                                                               (ch+1), msgdata[5],
+                                                                               map
+                                                                       );
+                                                                       return str;
+                                                               }
+                                                       }
+                                                       else if( (msgdata[5] & 0xF0) == 0x40 ) {
+                                                               int ch = (msgdata[5] & 0x0F);
+                                                               if( ch <= 9 ) ch--; else if( ch == 0 ) ch = 9;
+                                                               int dt = (msgdata[7] & 0xFF);
+                                                               if( msgdata[6]==0x20 ) {
+                                                                       str += String.format(
+                                                                               " [88] EQ: Ch=%d(0x%02X) %s",
+                                                                               (ch+1), msgdata[5],
+                                                                               dt==0 ? "OFF" : dt==1 ? "ON" : String.format("0x%02X",dt)
+                                                                       );
+                                                               }
+                                                               else if( msgdata[6]==0x22 ) {
+                                                                       str += String.format(
+                                                                               " [Pro] Part EFX Assign: Ch=%d(0x%02X) %s",
+                                                                               (ch+1), msgdata[5],
+                                                                               dt==0 ? "ByPass" : dt==1 ? "EFX" : String.format("0x%02X",dt)
+                                                                       );
+                                                               }
+                                                       }
+                                                       break;
+                                               } // [4]
+                                       } // [3] [DT1]
+                                       break; // [GS]
+                               case 0x45:
+                                       str += " [GS-LCD]"; dataBytePos++;
+                                       if( msgdata[3]==0x12 ) {
+                                               str += " [DT1]"; dataBytePos++;
+                                               if( msgdata[4]==0x10 && msgdata[5]==0x00 && msgdata[6]==0x00 ) {
+                                                       dataBytePos += 3;
+                                                       str += " Disp [" +(new String(
+                                                               msgdata, dataBytePos, msgdata.length - dataBytePos - 2
+                                                       ))+ "]";
+                                               }
+                                       } // [3] [DT1]
+                                       break;
+                               case 0x14: str += " [D-50]"; dataBytePos++; break;
+                               case 0x16: str += " [MT-32]"; dataBytePos++; break;
+                               } // [2] model_id
+                               break;
+                       case 0x43: // Yamaha
+                               if( (deviceId & 0xF0) == 0x10 && modelId == 0x4C ) {
+                                       str += " [XG]Dev#="+(deviceId & 0x0F);
+                                       dataBytePos += 2;
+                                       if( msgdata[3]==0 && msgdata[4]==0 && msgdata[5]==0x7E && msgdata[6]==0 ) {
+                                               str += " System ON";
+                                               return str;
+                                       }
+
+                               }
+                               else if( deviceId == 0x79 && modelId == 9 ) {
+                                       str += " [eVocaloid]";
+                                       dataBytePos += 2;
+                                       if( msgdata[3]==0x11 && msgdata[4]==0x0A && msgdata[5]==0 ) {
+                                               StringBuilder p = new StringBuilder();
+                                               for( int i=6; i<msgdata.length; i++ ) {
+                                                       int b = (msgdata[i] & 0xFF);
+                                                       if( b == 0xF7 ) break;
+                                                       String s = (
+                                                               b >= MIDISpec.nsx39LyricElements.length ?
+                                                               "?": MIDISpec.nsx39LyricElements[b]
+                                                       );
+                                                       p.append(s);
+                                               }
+                                               str += " pronounce["+p+"]";
+                                               return str;
+                                       }
+                               }
+                               break;
+                       default:
+                               break;
+                       }
+                       int i;
+                       str += " data=(";
+                       for( i = dataBytePos; i<msgdata.length-1; i++ ) {
+                               str += String.format( " %02X", msgdata[i] );
+                       }
+                       if( i < msgdata.length && (int)(msgdata[i] & 0xFF) != 0xF7 ) {
+                               str+=" [ Invalid EOX " + String.format( "%02X", msgdata[i] ) + " ]";
+                       }
+                       str += " )";
+                       return str;
+               }
+               byte[] msg_data = msg.getMessage();
+               str += "(";
+               for( byte b : msg_data ) {
+                       str += String.format( " %02X", b );
+               }
+               str += " )";
+               return str;
+       }
+       public static boolean isRhythmPart(int ch) { return (ch == 9); }
+}