OSDN Git Service

リファクタリング(Chordクラス周りを中心に)
[midichordhelper/MIDIChordHelper.git] / src / camidion / chordhelper / music / ChordProgression.java
index f505413..71ab49c 100644 (file)
-package camidion.chordhelper.music;\r
-\r
-import java.util.Vector;\r
-import java.util.regex.Pattern;\r
-\r
-import javax.sound.midi.InvalidMidiDataException;\r
-import javax.sound.midi.Sequence;\r
-\r
-/**\r
- * Chord Progression - コード進行のクラス\r
- */\r
-public class ChordProgression {\r
-\r
-       public class TickRange implements Cloneable {\r
-               long start_tick_pos = 0, end_tick_pos = 0;\r
-               public TickRange( long tick_pos ) {\r
-                       end_tick_pos = start_tick_pos = tick_pos;\r
-               }\r
-               public TickRange( long start_tick_pos, long end_tick_pos ) {\r
-                       this.start_tick_pos = start_tick_pos;\r
-                       this.end_tick_pos = end_tick_pos;\r
-               }\r
-               protected TickRange clone() {\r
-                       return new TickRange( start_tick_pos, end_tick_pos );\r
-               }\r
-               public void moveForward() {\r
-                       start_tick_pos = end_tick_pos;\r
-               }\r
-               public void moveForward( long duration ) {\r
-                       start_tick_pos = end_tick_pos;\r
-                       end_tick_pos += duration;\r
-               }\r
-               public long duration() {\r
-                       return end_tick_pos - start_tick_pos;\r
-               }\r
-               public boolean contains( long tick ) {\r
-                       return ( tick >= start_tick_pos && tick < end_tick_pos );\r
-               }\r
-       }\r
-\r
-       class ChordStroke {\r
-               Chord chord; int beat_length; TickRange tick_range = null;\r
-               public ChordStroke(Chord chord) { this( chord, 1 ); }\r
-               public ChordStroke(Chord chord, int beat_length) {\r
-                       this.chord = chord;\r
-                       this.beat_length = beat_length;\r
-               }\r
-               public String toString() {\r
-                       String str = chord.toString();\r
-                       for( int i=2; i <= beat_length; i++ ) str += " %";\r
-                       return str;\r
-               }\r
-       }\r
-\r
-       // 時間位置付き歌詞\r
-       public class Lyrics {\r
-               String text = null;\r
-               Long start_tick_pos = null;\r
-               public Lyrics(String text) { this.text = text; }\r
-               public Lyrics(String text, long tick_pos) {\r
-                       this.text = text; start_tick_pos = tick_pos;\r
-               }\r
-               public String toString() { return text; }\r
-       }\r
-\r
-       class Measure extends Vector<Object> {\r
-               Long ticks_per_beat = null;\r
-               public int numberOfBeats() {\r
-                       int n = 0;\r
-                       for( Object obj : this ) {\r
-                               if( obj instanceof ChordStroke ) {\r
-                                       n += ((ChordStroke)obj).beat_length;\r
-                               }\r
-                       }\r
-                       return n;\r
-               }\r
-               // 小節内のコードストロークが時間的に等間隔かどうか調べる。\r
-               // もし等間隔の場合、テキスト出力時に % をつける必要がなくなる。\r
-               public boolean isEquallyDivided() {\r
-                       int l, l_prev = 0;\r
-                       for( Object obj : this ) {\r
-                               if( obj instanceof ChordStroke ) {\r
-                                       l = ((ChordStroke)obj).beat_length;\r
-                                       if( l_prev > 0 && l_prev != l ) {\r
-                                               return false;\r
-                                       }\r
-                                       l_prev = l;\r
-                               }\r
-                       }\r
-                       return true;\r
-               }\r
-               public int addBeat() { return addBeat(1); }\r
-               public int addBeat(int num_beats) {\r
-                       ChordStroke last_chord_stroke = null;\r
-                       for( Object obj : this ) {\r
-                               if( obj instanceof ChordStroke ) {\r
-                                       last_chord_stroke = (ChordStroke)obj;\r
-                               }\r
-                       }\r
-                       if( last_chord_stroke == null ) {\r
-                               return 0;\r
-                       }\r
-                       return last_chord_stroke.beat_length += num_beats;\r
-               }\r
-               public String toString() {\r
-                       String str = "";\r
-                       boolean is_eq_dev = isEquallyDivided();\r
-                       for( Object element : this ) {\r
-                               str += " ";\r
-                               if( element instanceof ChordStroke ) {\r
-                                       ChordStroke cs = (ChordStroke)element;\r
-                                       str += is_eq_dev ? cs.chord : cs;\r
-                               }\r
-                               else if( element instanceof Lyrics ) {\r
-                                       str += element.toString();\r
-                               }\r
-                       }\r
-                       return str;\r
-               }\r
-               public TickRange getRange() {\r
-                       long start_tick_pos = -1;\r
-                       long end_tick_pos = -1;\r
-                       for( Object element : this ) {\r
-                               if( ! (element instanceof ChordProgression.ChordStroke) )\r
-                                       continue;\r
-                               ChordProgression.ChordStroke chord_stroke\r
-                               = (ChordProgression.ChordStroke)element;\r
-                               // 小節の先頭と末尾の tick を求める\r
-                               if( start_tick_pos < 0 ) {\r
-                                       start_tick_pos = chord_stroke.tick_range.start_tick_pos;\r
-                               }\r
-                               end_tick_pos = chord_stroke.tick_range.end_tick_pos;\r
-                       }\r
-                       if( start_tick_pos < 0 || end_tick_pos < 0 ) {\r
-                               return null;\r
-                       }\r
-                       return new TickRange( start_tick_pos, end_tick_pos );\r
-               }\r
-               public ChordStroke chordStrokeAt( long tick ) {\r
-                       for( Object element : this ) {\r
-                               if( ! (element instanceof ChordProgression.ChordStroke) )\r
-                                       continue;\r
-                               ChordProgression.ChordStroke chord_stroke\r
-                               = (ChordProgression.ChordStroke)element;\r
-                               if( chord_stroke.tick_range.contains(tick) ) {\r
-                                       return chord_stroke;\r
-                               }\r
-                       }\r
-                       return null;\r
-               }\r
-       }\r
-       class Line extends Vector<Measure> {\r
-               public String toString() {\r
-                       String str = "";\r
-                       for( Measure measure : this ) str += measure + "|";\r
-                       return str;\r
-               }\r
-       }\r
-\r
-       // 内部変数\r
-       Vector<Line> lines = null;\r
-       Key key = null;\r
-       private Long ticks_per_measure = null;\r
-\r
-       public Key getKey() { return key; }\r
-       public void setKey(Key key) { this.key = key; }\r
-\r
-       public String toString() {\r
-               String str = "";\r
-               if( key != null ) str += "Key: " + key + "\n";\r
-               for( Line line : lines ) str += line + "\n";\r
-               return str;\r
-       }\r
-\r
-       /**\r
-        * デフォルトの設定でコード進行を構築します。\r
-        */\r
-       public ChordProgression() { }\r
-       /**\r
-        * 指定された小節数、キー、拍子に合わせたコード進行を構築します。\r
-        * コード進行の内容は、ランダムに自動生成されます。\r
-        * @param measureLength 小節の長さ\r
-        * @param timeSignatureUpper 拍子の分子\r
-        */\r
-       public ChordProgression( int measureLength, int timeSignatureUpper ) {\r
-               int key_co5 = (int)(Math.random() * 12) - 5;\r
-               key = new Key( key_co5, Key.MAJOR );\r
-               lines = new Vector<Line>();\r
-               Line line = new Line();\r
-               boolean is_end;\r
-               Chord chord, prev_chord = new Chord(new NoteSymbol(key_co5));\r
-               int co5_offset, prev_co5_offset;\r
-               double r;\r
-               for( int mp=0; mp<measureLength; mp++ ) {\r
-                       is_end = (mp == 0 || mp == measureLength - 1); // 最初または最後の小節かを覚えておく\r
-                       Measure measure = new Measure();\r
-                       ChordStroke lastChordStroke = null;\r
-                       for( int i=0; i<timeSignatureUpper; i++ ) {\r
-                               if(\r
-                                       i % 4 == 2 && Math.random() < 0.8\r
-                                       ||\r
-                                       i % 2 != 0 && Math.random() < 0.9\r
-                               ){\r
-                                       // もう一拍延長\r
-                                       lastChordStroke.beat_length++;\r
-                                       continue;\r
-                               }\r
-                               chord = new Chord(new NoteSymbol(key_co5));\r
-                               co5_offset = 0;\r
-                               prev_co5_offset = prev_chord.rootNoteSymbol().toCo5() - key_co5;\r
-                               if( ! is_end ) {\r
-                                       //\r
-                                       // 最初または最後の小節は常にトニックにする。\r
-                                       // 完全五度ずつ下がる進行を基本としつつ、時々そうでない進行も出現するようにする。\r
-                                       // サブドミナントを超えるとスケールを外れるので、超えそうになったらランダムに決め直す。\r
-                                       //\r
-                                       r = Math.random();\r
-                                       co5_offset = prev_co5_offset - 1;\r
-                                       if( co5_offset < -1 || (prev_co5_offset < 5 && r < 0.5) ) {\r
-                                               //\r
-                                               // 長7度がルートとなるコードの出現確率を半減させながらコードを決める\r
-                                               // (余りが6のときだけが長7度)\r
-                                               // なお、前回と同じコードは使わないようにする。\r
-                                               do {\r
-                                                       co5_offset = (int)(Math.random() * 13) % 7 - 1;\r
-                                               } while( co5_offset == prev_co5_offset );\r
-                                       }\r
-                                       int co5RootNote = key_co5 + co5_offset;\r
-                                       chord.setRoot(new NoteSymbol(co5RootNote));\r
-                                       chord.setBass(new NoteSymbol(co5RootNote));\r
-                               }\r
-                               switch( co5_offset ) {\r
-                               // ルート音ごとに、7th などの付加や、メジャーマイナー反転を行う確率を決める\r
-                               case 5: // VII\r
-                                       if( Math.random() < 0.5 ) {\r
-                                               // m7-5\r
-                                               chord.set(Chord.Interval.MINOR);\r
-                                               chord.set(Chord.Interval.FLAT5);\r
-                                       }\r
-                                       if( Math.random() < 0.8 )\r
-                                               chord.set(Chord.Interval.SEVENTH);\r
-                                       break;\r
-                               case 4: // Secondary dominant (III)\r
-                                       if( prev_co5_offset == 5 ) {\r
-                                               // ルートが長7度→長3度の進行のとき、反転確率を上げる。\r
-                                               // (ハ長調でいう Bm7-5 の次に E7 を出現しやすくする)\r
-                                               if( Math.random() < 0.2 ) chord.set(Chord.Interval.MINOR);\r
-                                       }\r
-                                       else {\r
-                                               if( Math.random() < 0.8 ) chord.set(Chord.Interval.MINOR);\r
-                                       }\r
-                                       if( Math.random() < 0.7 ) chord.set(Chord.Interval.SEVENTH);\r
-                                       break;\r
-                               case 3: // VI\r
-                                       if( Math.random() < 0.8 ) chord.set(Chord.Interval.MINOR);\r
-                                       if( Math.random() < 0.7 ) chord.set(Chord.Interval.SEVENTH);\r
-                                       break;\r
-                               case 2: // II\r
-                                       if( Math.random() < 0.8 ) chord.set(Chord.Interval.MINOR);\r
-                                       if( Math.random() < 0.7 ) chord.set(Chord.Interval.SEVENTH);\r
-                                       break;\r
-                               case 1: // Dominant (V)\r
-                                       if( Math.random() < 0.1 ) chord.set(Chord.Interval.MINOR);\r
-                                       if( Math.random() < 0.3 ) chord.set(Chord.Interval.SEVENTH);\r
-                                       if( Math.random() < 0.2 ) chord.set(Chord.Interval.NINTH);\r
-                                       break;\r
-                               case 0: // Tonic(ここでマイナーで終わるとさみしいので setMinorThird() はしない)\r
-                                       if( Math.random() < 0.2 ) chord.set(Chord.Interval.MAJOR_SEVENTH);\r
-                                       if( Math.random() < 0.2 ) chord.set(Chord.Interval.NINTH);\r
-                                       break;\r
-                               case -1: // Sub-dominant (IV)\r
-                                       if( Math.random() < 0.1 ) {\r
-                                               chord.set(Chord.Interval.MINOR);\r
-                                               if( Math.random() < 0.3 ) chord.set(Chord.Interval.SEVENTH);\r
-                                       }\r
-                                       else\r
-                                               if( Math.random() < 0.2 ) chord.set(Chord.Interval.MAJOR_SEVENTH);\r
-                                       if( Math.random() < 0.2 ) chord.set(Chord.Interval.NINTH);\r
-                                       break;\r
-                               }\r
-                               measure.add( lastChordStroke = new ChordStroke(chord) );\r
-                               prev_chord = chord;\r
-                       }\r
-                       line.add(measure);\r
-                       if( (mp+1) % 8 == 0 ) { // 8小節おきに改行\r
-                               lines.add(line);\r
-                               line = new Line();\r
-                       }\r
-               }\r
-               if( line.size() > 0 ) lines.add(line);\r
-       }\r
-       // テキストからコード進行を生成\r
-       public ChordProgression( String source_text ) {\r
-               if( source_text == null ) return;\r
-               Measure measure;\r
-               Line line;\r
-               String[] lines_src, measures_src, elements_src;\r
-               Chord last_chord = null;\r
-               String key_regex = "^Key(\\s*):(\\s*)";\r
-               //\r
-               // キーであるかどうか見分けるためのパターン\r
-               Pattern key_match_pattern = Pattern.compile(\r
-                               key_regex+".*$", Pattern.CASE_INSENSITIVE\r
-                               );\r
-               // キーのヘッダーを取り除くためのパターン\r
-               Pattern key_repl_pattern = Pattern.compile(\r
-                               key_regex, Pattern.CASE_INSENSITIVE\r
-                               );\r
-               //\r
-               lines_src = source_text.split("[\r\n]+");\r
-               lines = new Vector<Line>();\r
-               for( String line_src : lines_src ) {\r
-                       measures_src = line_src.split("\\|");\r
-                       if( measures_src.length > 0 ) {\r
-                               String key_string = measures_src[0].trim();\r
-                               if( key_match_pattern.matcher(key_string).matches() ) {\r
-                                       key = new Key(\r
-                                                       key_repl_pattern.matcher(key_string).replaceFirst("")\r
-                                                       );\r
-                                       // System.out.println("Key = " + key);\r
-                                       continue;\r
-                               }\r
-                       }\r
-                       line = new Line();\r
-                       for( String measure_src : measures_src ) {\r
-                               elements_src = measure_src.split("[ \t]+");\r
-                               measure = new Measure();\r
-                               for( String element_src : elements_src ) {\r
-                                       if( element_src.isEmpty() ) continue;\r
-                                       if( element_src.equals("%") ) {\r
-                                               if( measure.addBeat() == 0 ) {\r
-                                                       measure.add( new ChordStroke(last_chord) );\r
-                                               }\r
-                                               continue;\r
-                                       }\r
-                                       try {\r
-                                               measure.add( new ChordStroke(\r
-                                                               last_chord = new Chord(element_src)\r
-                                                               ));\r
-                                       } catch( IllegalArgumentException ex ) {\r
-                                               measure.add( new Lyrics(element_src) );\r
-                                       }\r
-                               }\r
-                               line.add(measure);\r
-                       }\r
-                       lines.add(line);\r
-               }\r
-       }\r
-\r
-       // Major/minor 切り替え\r
-       public void toggleKeyMajorMinor() {\r
-               key = key.relativeKey();\r
-       }\r
-\r
-       // コード進行の移調\r
-       public void transpose(int chromatic_offset) {\r
-               for( Line line : lines ) {\r
-                       for( Measure measure : line ) {\r
-                               for( int i=0; i<measure.size(); i++ ) {\r
-                                       Object element = measure.get(i);\r
-                                       if( element instanceof ChordStroke ) {\r
-                                               ChordStroke cs = (ChordStroke)element;\r
-                                               Chord new_chord = cs.chord.clone();\r
-                                               //\r
-                                               // キーが未設定のときは、最初のコードから推測して設定\r
-                                               if( key == null ) key = new Key( new_chord );\r
-                                               //\r
-                                               new_chord.transpose( chromatic_offset, key );\r
-                                               measure.set( i, new ChordStroke( new_chord, cs.beat_length ) );\r
-                                       }\r
-                               }\r
-                       }\r
-               }\r
-               key.transpose(chromatic_offset);\r
-       }\r
-       // 異名同音の♭と#を切り替える\r
-       public void toggleEnharmonically() {\r
-               if( key == null ) return;\r
-               int original_key_co5 = key.toCo5();\r
-               int co5Offset = 0;\r
-               if( original_key_co5 > 4 ) {\r
-                       co5Offset = -Music.SEMITONES_PER_OCTAVE;\r
-               }\r
-               else if( original_key_co5 < -4 ) {\r
-                       co5Offset = Music.SEMITONES_PER_OCTAVE;\r
-               }\r
-               else {\r
-                       return;\r
-               }\r
-               key.toggleEnharmonically();\r
-               for( Line line : lines ) {\r
-                       for( Measure measure : line ) {\r
-                               for( int i=0; i<measure.size(); i++ ) {\r
-                                       Object element = measure.get(i);\r
-                                       if( element instanceof ChordStroke ) {\r
-                                               ChordStroke cs = (ChordStroke)element;\r
-                                               Chord newChord = cs.chord.clone();\r
-                                               newChord.setRoot(new NoteSymbol(newChord.rootNoteSymbol().toCo5() + co5Offset));\r
-                                               newChord.setBass(new NoteSymbol(newChord.bassNoteSymbol().toCo5() + co5Offset));\r
-                                               measure.set( i, new ChordStroke( newChord, cs.beat_length ) );\r
-                                       }\r
-                               }\r
-                       }\r
-               }\r
-       }\r
-       // コード進行の中に時間軸(MIDI tick)を書き込む\r
-       //\r
-       public void setTickPositions( FirstTrackSpec first_track ) {\r
-               ticks_per_measure = first_track.ticks_per_measure;\r
-               TickRange tick_range = new TickRange(\r
-                               first_track.pre_measures * ticks_per_measure\r
-                               );\r
-               for( Line line : lines ) { // 行単位の処理\r
-                       for( Measure measure : line ) { // 小節単位の処理\r
-                               int n_beats = measure.numberOfBeats();\r
-                               if( n_beats == 0 ) continue;\r
-                               long tpb = measure.ticks_per_beat = ticks_per_measure / n_beats ;\r
-                               for( Object element : measure ) {\r
-                                       if( element instanceof Lyrics ) {\r
-                                               ((Lyrics)element).start_tick_pos = tick_range.start_tick_pos;\r
-                                               continue;\r
-                                       }\r
-                                       else if( element instanceof ChordStroke ) {\r
-                                               ChordStroke chord_stroke = (ChordStroke)element;\r
-                                               tick_range.moveForward( tpb * chord_stroke.beat_length );\r
-                                               chord_stroke.tick_range = tick_range.clone();\r
-                                       }\r
-                               }\r
-                       }\r
-               }\r
-       }\r
-       // コード文字列の書き込み\r
-       public void setChordSymbolTextTo( AbstractTrackSpec ts ) {\r
-               for( Line line : lines ) {\r
-                       for( Measure measure : line ) {\r
-                               if( measure.ticks_per_beat == null ) continue;\r
-                               for( Object element : measure ) {\r
-                                       if( element instanceof ChordStroke ) {\r
-                                               ts.addStringTo( 0x01, (ChordStroke)element );\r
-                                       }\r
-                               }\r
-                       }\r
-               }\r
-       }\r
-       // 歌詞の書き込み\r
-       public void setLyricsTo( AbstractTrackSpec ts ) {\r
-               for( Line line : lines ) {\r
-                       for( Measure measure : line ) {\r
-                               if( measure.ticks_per_beat == null ) continue;\r
-                               for( Object element : measure ) {\r
-                                       if( element instanceof Lyrics ) {\r
-                                               ts.addStringTo( 0x05, (Lyrics)element );\r
-                                       }\r
-                               }\r
-                       }\r
-               }\r
-       }\r
-       /**\r
-        * コード進行をもとに MIDI シーケンスを生成します。\r
-        * @return MIDIシーケンス\r
-        */\r
-       public Sequence toMidiSequence() {\r
-               return toMidiSequence(48);\r
-       }\r
-       /**\r
-        * 指定のタイミング解像度で、\r
-        * コード進行をもとに MIDI シーケンスを生成します。\r
-        * @return MIDIシーケンス\r
-        */\r
-       public Sequence toMidiSequence(int ppq) {\r
-               //\r
-               // PPQ = Pulse Per Quarter (TPQN = Tick Per Quearter Note)\r
-               //\r
-               return toMidiSequence( ppq, 0, 0, null, null );\r
-       }\r
-       /**\r
-        * 小節数、トラック仕様、コード進行をもとに MIDI シーケンスを生成します。\r
-        * @return MIDIシーケンス\r
-        */\r
-       public Sequence toMidiSequence(\r
-               int ppq, int start_measure_pos, int end_measure_pos,\r
-               FirstTrackSpec first_track,\r
-               Vector<AbstractNoteTrackSpec> track_specs\r
-       ) {\r
-               Sequence seq;\r
-               try {\r
-                       seq = new Sequence(Sequence.PPQ, ppq);\r
-               } catch ( InvalidMidiDataException e ) {\r
-                       e.printStackTrace();\r
-                       return null;\r
-               }\r
-               // マスタートラックの生成\r
-               if( first_track == null ) {\r
-                       first_track = new FirstTrackSpec();\r
-               }\r
-               first_track.key = this.key;\r
-               first_track.createTrack( seq, start_measure_pos, end_measure_pos );\r
-               //\r
-               // 中身がなければここで終了\r
-               if( lines == null || track_specs == null ) return seq;\r
-               //\r
-               // コード進行の中に時間軸(MIDI tick)を書き込む\r
-               setTickPositions( first_track );\r
-               //\r
-               // コードのテキストと歌詞を書き込む\r
-               setChordSymbolTextTo( first_track );\r
-               setLyricsTo( first_track );\r
-               //\r
-               // 残りのトラックを生成\r
-               for( AbstractNoteTrackSpec ts : track_specs ) {\r
-                       ts.createTrack( seq, first_track );\r
-                       if( ts instanceof DrumTrackSpec ) {\r
-                               ((DrumTrackSpec)ts).addDrums(this);\r
-                       }\r
-                       else {\r
-                               ((MelodyTrackSpec)ts).addChords(this);\r
-                       }\r
-               }\r
-               return seq;\r
-       }\r
+package camidion.chordhelper.music;
+
+import java.util.Vector;
+import java.util.regex.Pattern;
+
+import javax.sound.midi.InvalidMidiDataException;
+import javax.sound.midi.Sequence;
+
+/**
+ * Chord Progression - コード進行のクラス
+ */
+public class ChordProgression {
+
+       public class TickRange implements Cloneable {
+               long startTickPos = 0, end_tick_pos = 0;
+               public TickRange( long tick_pos ) {
+                       end_tick_pos = startTickPos = tick_pos;
+               }
+               public TickRange( long start_tick_pos, long end_tick_pos ) {
+                       this.startTickPos = start_tick_pos;
+                       this.end_tick_pos = end_tick_pos;
+               }
+               protected TickRange clone() {
+                       return new TickRange( startTickPos, end_tick_pos );
+               }
+               public void moveForward() {
+                       startTickPos = end_tick_pos;
+               }
+               public void moveForward( long duration ) {
+                       startTickPos = end_tick_pos;
+                       end_tick_pos += duration;
+               }
+               public long duration() {
+                       return end_tick_pos - startTickPos;
+               }
+               public boolean contains( long tick ) {
+                       return ( tick >= startTickPos && tick < end_tick_pos );
+               }
+       }
+
+       class ChordStroke {
+               Chord chord; int beatLength; TickRange tickRange = null;
+               public ChordStroke(Chord chord) { this( chord, 1 ); }
+               public ChordStroke(Chord chord, int beat_length) {
+                       this.chord = chord;
+                       this.beatLength = beat_length;
+               }
+               public String toString() {
+                       String str = chord.toString();
+                       for( int i=2; i <= beatLength; i++ ) str += " %";
+                       return str;
+               }
+       }
+
+       // 時間位置付き歌詞
+       public class Lyrics {
+               String text = null;
+               Long startTickPos = null;
+               public Lyrics(String text) { this.text = text; }
+               public Lyrics(String text, long tick_pos) {
+                       this.text = text; startTickPos = tick_pos;
+               }
+               public String toString() { return text; }
+       }
+
+       class Measure extends Vector<Object> {
+               Long ticks_per_beat = null;
+               public int numberOfBeats() {
+                       int n = 0;
+                       for( Object obj : this ) {
+                               if( obj instanceof ChordStroke ) {
+                                       n += ((ChordStroke)obj).beatLength;
+                               }
+                       }
+                       return n;
+               }
+               // 小節内のコードストロークが時間的に等間隔かどうか調べる。
+               // もし等間隔の場合、テキスト出力時に % をつける必要がなくなる。
+               public boolean isEquallyDivided() {
+                       int l, l_prev = 0;
+                       for( Object obj : this ) {
+                               if( obj instanceof ChordStroke ) {
+                                       l = ((ChordStroke)obj).beatLength;
+                                       if( l_prev > 0 && l_prev != l ) {
+                                               return false;
+                                       }
+                                       l_prev = l;
+                               }
+                       }
+                       return true;
+               }
+               public int addBeat() { return addBeat(1); }
+               public int addBeat(int num_beats) {
+                       ChordStroke last_chord_stroke = null;
+                       for( Object obj : this ) {
+                               if( obj instanceof ChordStroke ) {
+                                       last_chord_stroke = (ChordStroke)obj;
+                               }
+                       }
+                       if( last_chord_stroke == null ) {
+                               return 0;
+                       }
+                       return last_chord_stroke.beatLength += num_beats;
+               }
+               public String toString() {
+                       String str = "";
+                       boolean is_eq_dev = isEquallyDivided();
+                       for( Object element : this ) {
+                               str += " ";
+                               if( element instanceof ChordStroke ) {
+                                       ChordStroke cs = (ChordStroke)element;
+                                       str += is_eq_dev ? cs.chord : cs;
+                               }
+                               else if( element instanceof Lyrics ) {
+                                       str += element.toString();
+                               }
+                       }
+                       return str;
+               }
+               public TickRange getRange() {
+                       long start_tick_pos = -1;
+                       long end_tick_pos = -1;
+                       for( Object element : this ) {
+                               if( ! (element instanceof ChordProgression.ChordStroke) )
+                                       continue;
+                               ChordProgression.ChordStroke chord_stroke
+                               = (ChordProgression.ChordStroke)element;
+                               // 小節の先頭と末尾の tick を求める
+                               if( start_tick_pos < 0 ) {
+                                       start_tick_pos = chord_stroke.tickRange.startTickPos;
+                               }
+                               end_tick_pos = chord_stroke.tickRange.end_tick_pos;
+                       }
+                       if( start_tick_pos < 0 || end_tick_pos < 0 ) {
+                               return null;
+                       }
+                       return new TickRange( start_tick_pos, end_tick_pos );
+               }
+               public ChordStroke chordStrokeAt( long tick ) {
+                       for( Object element : this ) {
+                               if( ! (element instanceof ChordProgression.ChordStroke) )
+                                       continue;
+                               ChordProgression.ChordStroke chord_stroke
+                               = (ChordProgression.ChordStroke)element;
+                               if( chord_stroke.tickRange.contains(tick) ) {
+                                       return chord_stroke;
+                               }
+                       }
+                       return null;
+               }
+       }
+       class Line extends Vector<Measure> {
+               public String toString() {
+                       String str = "";
+                       for( Measure measure : this ) str += measure + "|";
+                       return str;
+               }
+       }
+
+       // 内部変数
+       Vector<Line> lines = null;
+       Key key = null;
+       private Long ticks_per_measure = null;
+
+       public Key getKey() { return key; }
+       public void setKey(Key key) { this.key = key; }
+
+       public String toString() {
+               String str = "";
+               if( key != null ) str += "Key: " + key + "\n";
+               for( Line line : lines ) str += line + "\n";
+               return str;
+       }
+
+       /**
+        * デフォルトの設定でコード進行を構築します。
+        */
+       public ChordProgression() { }
+       /**
+        * 指定された小節数、キー、拍子に合わせたコード進行を構築します。
+        * コード進行の内容は、ランダムに自動生成されます。
+        * @param measureLength 小節の長さ
+        * @param timeSignatureUpper 拍子の分子
+        */
+       public ChordProgression( int measureLength, int timeSignatureUpper ) {
+               int keyCo5 = (int)(Math.random() * 12) - 5;
+               key = new Key(keyCo5, Key.MajorMinor.MAJOR);
+               lines = new Vector<Line>();
+               Line line = new Line();
+               boolean is_end;
+               Chord chord, prevChord = new Chord(new NoteSymbol(keyCo5));
+               int co5Offset, prevCo5Offset;
+               double r;
+               for( int mp=0; mp<measureLength; mp++ ) {
+                       is_end = (mp == 0 || mp == measureLength - 1); // 最初または最後の小節かを覚えておく
+                       Measure measure = new Measure();
+                       ChordStroke lastChordStroke = null;
+                       for( int i=0; i<timeSignatureUpper; i++ ) {
+                               if(
+                                       i % 4 == 2 && Math.random() < 0.8
+                                       ||
+                                       i % 2 != 0 && Math.random() < 0.9
+                               ){
+                                       // もう一拍延長
+                                       lastChordStroke.beatLength++;
+                                       continue;
+                               }
+                               co5Offset = 0;
+                               prevCo5Offset = prevChord.rootNoteSymbol().toCo5() - keyCo5;
+                               if( ! is_end ) {
+                                       //
+                                       // 最初または最後の小節は常にトニックにする。
+                                       // 完全五度ずつ下がる進行を基本としつつ、時々そうでない進行も出現するようにする。
+                                       // サブドミナントを超えるとスケールを外れるので、超えそうになったらランダムに決め直す。
+                                       //
+                                       r = Math.random();
+                                       co5Offset = prevCo5Offset - 1;
+                                       if( co5Offset < -1 || (prevCo5Offset < 5 && r < 0.5) ) {
+                                               //
+                                               // 長7度がルートとなるコードの出現確率を半減させながらコードを決める
+                                               // (余りが6のときだけが長7度)
+                                               // なお、前回と同じコードは使わないようにする。
+                                               do {
+                                                       co5Offset = (int)(Math.random() * 13) % 7 - 1;
+                                               } while( co5Offset == prevCo5Offset );
+                                       }
+                               }
+                               chord = new Chord(new NoteSymbol(keyCo5 + co5Offset));
+                               switch(co5Offset) {
+                               // ルート音ごとに、7th などの付加や、メジャーマイナー反転を行う確率を決める
+                               case 5: // VII
+                                       if( Math.random() < 0.5 ) {
+                                               // m7-5
+                                               chord.set(Chord.Interval.MINOR);
+                                               chord.set(Chord.Interval.FLAT5);
+                                       }
+                                       if( Math.random() < 0.8 )
+                                               chord.set(Chord.Interval.SEVENTH);
+                                       break;
+                               case 4: // Secondary dominant (III)
+                                       if( prevCo5Offset == 5 ) {
+                                               // ルートが長7度→長3度の進行のとき、反転確率を上げる。
+                                               // (ハ長調でいう Bm7-5 の次に E7 を出現しやすくする)
+                                               if( Math.random() < 0.2 ) chord.set(Chord.Interval.MINOR);
+                                       }
+                                       else {
+                                               if( Math.random() < 0.8 ) chord.set(Chord.Interval.MINOR);
+                                       }
+                                       if( Math.random() < 0.7 ) chord.set(Chord.Interval.SEVENTH);
+                                       break;
+                               case 3: // VI
+                                       if( Math.random() < 0.8 ) chord.set(Chord.Interval.MINOR);
+                                       if( Math.random() < 0.7 ) chord.set(Chord.Interval.SEVENTH);
+                                       break;
+                               case 2: // II
+                                       if( Math.random() < 0.8 ) chord.set(Chord.Interval.MINOR);
+                                       if( Math.random() < 0.7 ) chord.set(Chord.Interval.SEVENTH);
+                                       break;
+                               case 1: // Dominant (V)
+                                       if( Math.random() < 0.1 ) chord.set(Chord.Interval.MINOR);
+                                       if( Math.random() < 0.3 ) chord.set(Chord.Interval.SEVENTH);
+                                       if( Math.random() < 0.2 ) chord.set(Chord.Interval.NINTH);
+                                       break;
+                               case 0: // Tonic(ここでマイナーで終わるとさみしいので setMinorThird() はしない)
+                                       if( Math.random() < 0.2 ) chord.set(Chord.Interval.MAJOR_SEVENTH);
+                                       if( Math.random() < 0.2 ) chord.set(Chord.Interval.NINTH);
+                                       break;
+                               case -1: // Sub-dominant (IV)
+                                       if( Math.random() < 0.1 ) {
+                                               chord.set(Chord.Interval.MINOR);
+                                               if( Math.random() < 0.3 ) chord.set(Chord.Interval.SEVENTH);
+                                       }
+                                       else
+                                               if( Math.random() < 0.2 ) chord.set(Chord.Interval.MAJOR_SEVENTH);
+                                       if( Math.random() < 0.2 ) chord.set(Chord.Interval.NINTH);
+                                       break;
+                               }
+                               measure.add( lastChordStroke = new ChordStroke(chord) );
+                               prevChord = chord;
+                       }
+                       line.add(measure);
+                       if( (mp+1) % 8 == 0 ) { // 8小節おきに改行
+                               lines.add(line);
+                               line = new Line();
+                       }
+               }
+               if( line.size() > 0 ) lines.add(line);
+       }
+       // テキストからコード進行を生成
+       public ChordProgression( String source_text ) {
+               if( source_text == null ) return;
+               Measure measure;
+               Line line;
+               String[] linesSrc, measuresSrc, elementsSrc;
+               Chord lastChord = null;
+               String keyHeaderRegex = "^Key(\\s*):(\\s*)";
+               String keyValueRegex = "[A-G]+.*$";
+               //
+               // キーであるかどうか見分けるためのパターン
+               Pattern keyMatchPattern = Pattern.compile(
+                       keyHeaderRegex + keyValueRegex,
+                       Pattern.CASE_INSENSITIVE
+               );
+               // キーのヘッダーを取り除くためのパターン
+               Pattern keyReplPattern = Pattern.compile(
+                       keyHeaderRegex, Pattern.CASE_INSENSITIVE
+               );
+               //
+               linesSrc = source_text.split("[\r\n]+");
+               lines = new Vector<Line>();
+               for( String line_src : linesSrc ) {
+                       measuresSrc = line_src.split("\\|");
+                       if( measuresSrc.length > 0 ) {
+                               String keyString = measuresSrc[0].trim();
+                               if( keyMatchPattern.matcher(keyString).matches() ) {
+                                       try {
+                                               key = new Key(keyReplPattern.matcher(keyString).replaceFirst(""));
+                                               continue;
+                                       } catch( Exception e ) {
+                                               e.printStackTrace();
+                                       }
+                               }
+                       }
+                       line = new Line();
+                       for( String measureSrc : measuresSrc ) {
+                               elementsSrc = measureSrc.split("[ \t]+");
+                               measure = new Measure();
+                               for( String elementSrc : elementsSrc ) {
+                                       if( elementSrc.isEmpty() ) continue;
+                                       if( elementSrc.equals("%") ) {
+                                               if( measure.addBeat() == 0 ) {
+                                                       measure.add( new ChordStroke(lastChord) );
+                                               }
+                                               continue;
+                                       }
+                                       try {
+                                               measure.add(new ChordStroke(lastChord = new Chord(elementSrc)));
+                                       } catch( IllegalArgumentException ex ) {
+                                               measure.add( new Lyrics(elementSrc) );
+                                       }
+                               }
+                               line.add(measure);
+                       }
+                       lines.add(line);
+               }
+       }
+
+       // Major/minor 切り替え
+       public void toggleKeyMajorMinor() { key = key.relativeKey(); }
+
+       // コード進行の移調
+       public void transpose(int chromaticOffset) {
+               for( Line line : lines ) {
+                       for( Measure measure : line ) {
+                               for( int i=0; i<measure.size(); i++ ) {
+                                       Object element = measure.get(i);
+                                       if( element instanceof ChordStroke ) {
+                                               ChordStroke cs = (ChordStroke)element;
+                                               //
+                                               // キーが未設定のときは、最初のコードから推測して設定
+                                               if( key == null ) key = new Key(cs.chord);
+                                               //
+                                               Chord newChord = cs.chord.transposedChord(chromaticOffset, key);
+                                               measure.set(i, new ChordStroke(newChord, cs.beatLength));
+                                       }
+                               }
+                       }
+               }
+               key = key.transposedKey(chromaticOffset);
+       }
+       // 異名同音の♭と♯を切り替える
+       public void toggleEnharmonically() {
+               if( key == null ) return;
+               int original_key_co5 = key.toCo5();
+               int co5Offset = 0;
+               if( original_key_co5 > 4 ) {
+                       co5Offset = -Music.SEMITONES_PER_OCTAVE;
+               }
+               else if( original_key_co5 < -4 ) {
+                       co5Offset = Music.SEMITONES_PER_OCTAVE;
+               }
+               else {
+                       return;
+               }
+               key = key.enharmonicKey();
+               for( Line line : lines ) {
+                       for( Measure measure : line ) {
+                               for( int i=0; i<measure.size(); i++ ) {
+                                       Object element = measure.get(i);
+                                       if( element instanceof ChordStroke ) {
+                                               ChordStroke cs = (ChordStroke)element;
+                                               NoteSymbol root = cs.chord.rootNoteSymbol();
+                                               NoteSymbol bass = cs.chord.bassNoteSymbol();
+                                               if( root.equals(bass) ) {
+                                                       bass = root = new NoteSymbol(root.toCo5() + co5Offset);
+                                               } else {
+                                                       root = new NoteSymbol(root.toCo5() + co5Offset);
+                                                       bass = new NoteSymbol(bass.toCo5() + co5Offset);
+                                               }
+                                               Chord newChord = new Chord(root, bass, cs.chord.intervals());
+                                               measure.set(i, new ChordStroke(newChord, cs.beatLength));
+                                       }
+                               }
+                       }
+               }
+       }
+       // コード進行の中に時間軸(MIDI tick)を書き込む
+       //
+       public void setTickPositions( FirstTrackSpec first_track ) {
+               ticks_per_measure = first_track.ticksPerMeasure;
+               TickRange tick_range = new TickRange(
+                               first_track.preMeasures * ticks_per_measure
+                               );
+               for( Line line : lines ) { // 行単位の処理
+                       for( Measure measure : line ) { // 小節単位の処理
+                               int n_beats = measure.numberOfBeats();
+                               if( n_beats == 0 ) continue;
+                               long tpb = measure.ticks_per_beat = ticks_per_measure / n_beats ;
+                               for( Object element : measure ) {
+                                       if( element instanceof Lyrics ) {
+                                               ((Lyrics)element).startTickPos = tick_range.startTickPos;
+                                               continue;
+                                       }
+                                       else if( element instanceof ChordStroke ) {
+                                               ChordStroke chord_stroke = (ChordStroke)element;
+                                               tick_range.moveForward( tpb * chord_stroke.beatLength );
+                                               chord_stroke.tickRange = tick_range.clone();
+                                       }
+                               }
+                       }
+               }
+       }
+       // コード文字列の書き込み
+       public void setChordSymbolTextTo( AbstractTrackSpec ts ) {
+               for( Line line : lines ) {
+                       for( Measure measure : line ) {
+                               if( measure.ticks_per_beat == null ) continue;
+                               for( Object element : measure ) {
+                                       if( element instanceof ChordStroke ) {
+                                               ts.addStringTo( 0x01, (ChordStroke)element );
+                                       }
+                               }
+                       }
+               }
+       }
+       // 歌詞の書き込み
+       public void setLyricsTo( AbstractTrackSpec ts ) {
+               for( Line line : lines ) {
+                       for( Measure measure : line ) {
+                               if( measure.ticks_per_beat == null ) continue;
+                               for( Object element : measure ) {
+                                       if( element instanceof Lyrics ) {
+                                               ts.addStringTo( 0x05, (Lyrics)element );
+                                       }
+                               }
+                       }
+               }
+       }
+       /**
+        * コード進行をもとに MIDI シーケンスを生成します。
+        * @return MIDIシーケンス
+        */
+       public Sequence toMidiSequence() {
+               return toMidiSequence(48);
+       }
+       /**
+        * 指定のタイミング解像度で、
+        * コード進行をもとに MIDI シーケンスを生成します。
+        * @return MIDIシーケンス
+        */
+       public Sequence toMidiSequence(int ppq) {
+               //
+               // PPQ = Pulse Per Quarter (TPQN = Tick Per Quearter Note)
+               //
+               return toMidiSequence( ppq, 0, 0, null, null );
+       }
+       /**
+        * 小節数、トラック仕様、コード進行をもとに MIDI シーケンスを生成します。
+        * @param ppq 分解能(pulse per quarter)
+        * @param startMeasure 開始小節位置
+        * @param endMeasure 終了小節位置
+        * @param firstTrack 最初のトラックの仕様
+        * @param trackSpecs 残りのトラックの仕様
+        * @return MIDIシーケンス
+        */
+       public Sequence toMidiSequence(
+               int ppq, int startMeasure, int endMeasure,
+               FirstTrackSpec firstTrack,
+               Vector<AbstractNoteTrackSpec> trackSpecs
+       ) {
+               Sequence seq;
+               try {
+                       seq = new Sequence(Sequence.PPQ, ppq);
+               } catch ( InvalidMidiDataException e ) {
+                       e.printStackTrace();
+                       return null;
+               }
+               // マスタートラックの生成
+               if( firstTrack == null ) {
+                       firstTrack = new FirstTrackSpec();
+               }
+               firstTrack.key = this.key;
+               firstTrack.createTrack( seq, startMeasure, endMeasure );
+               //
+               // 中身がなければここで終了
+               if( lines == null || trackSpecs == null ) return seq;
+               //
+               // コード進行の中に時間軸(MIDI tick)を書き込む
+               setTickPositions(firstTrack);
+               //
+               // コードのテキストと歌詞を書き込む
+               setChordSymbolTextTo(firstTrack);
+               setLyricsTo(firstTrack);
+               //
+               // 残りのトラックを生成
+               for( AbstractNoteTrackSpec ts : trackSpecs ) {
+                       ts.createTrack(seq, firstTrack);
+                       if( ts instanceof DrumTrackSpec ) {
+                               ((DrumTrackSpec)ts).addDrums(this);
+                       }
+                       else {
+                               ((MelodyTrackSpec)ts).addChords(this);
+                       }
+               }
+               return seq;
+       }
 }
\ No newline at end of file