-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); }
+}