-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