1 package camidion.chordhelper.music;
3 import java.util.Vector;
4 import java.util.regex.Pattern;
6 import javax.sound.midi.InvalidMidiDataException;
7 import javax.sound.midi.Sequence;
10 * Chord Progression - コード進行のクラス
12 public class ChordProgression {
14 public class TickRange implements Cloneable {
15 long startTickPos = 0, end_tick_pos = 0;
16 public TickRange( long tick_pos ) {
17 end_tick_pos = startTickPos = tick_pos;
19 public TickRange( long start_tick_pos, long end_tick_pos ) {
20 this.startTickPos = start_tick_pos;
21 this.end_tick_pos = end_tick_pos;
23 protected TickRange clone() {
24 return new TickRange( startTickPos, end_tick_pos );
26 public void moveForward() {
27 startTickPos = end_tick_pos;
29 public void moveForward( long duration ) {
30 startTickPos = end_tick_pos;
31 end_tick_pos += duration;
33 public long duration() {
34 return end_tick_pos - startTickPos;
36 public boolean contains( long tick ) {
37 return ( tick >= startTickPos && tick < end_tick_pos );
42 Chord chord; int beat_length; TickRange tickRange = null;
43 public ChordStroke(Chord chord) { this( chord, 1 ); }
44 public ChordStroke(Chord chord, int beat_length) {
46 this.beat_length = beat_length;
48 public String toString() {
49 String str = chord.toString();
50 for( int i=2; i <= beat_length; i++ ) str += " %";
58 Long startTickPos = null;
59 public Lyrics(String text) { this.text = text; }
60 public Lyrics(String text, long tick_pos) {
61 this.text = text; startTickPos = tick_pos;
63 public String toString() { return text; }
66 class Measure extends Vector<Object> {
67 Long ticks_per_beat = null;
68 public int numberOfBeats() {
70 for( Object obj : this ) {
71 if( obj instanceof ChordStroke ) {
72 n += ((ChordStroke)obj).beat_length;
77 // 小節内のコードストロークが時間的に等間隔かどうか調べる。
78 // もし等間隔の場合、テキスト出力時に % をつける必要がなくなる。
79 public boolean isEquallyDivided() {
81 for( Object obj : this ) {
82 if( obj instanceof ChordStroke ) {
83 l = ((ChordStroke)obj).beat_length;
84 if( l_prev > 0 && l_prev != l ) {
92 public int addBeat() { return addBeat(1); }
93 public int addBeat(int num_beats) {
94 ChordStroke last_chord_stroke = null;
95 for( Object obj : this ) {
96 if( obj instanceof ChordStroke ) {
97 last_chord_stroke = (ChordStroke)obj;
100 if( last_chord_stroke == null ) {
103 return last_chord_stroke.beat_length += num_beats;
105 public String toString() {
107 boolean is_eq_dev = isEquallyDivided();
108 for( Object element : this ) {
110 if( element instanceof ChordStroke ) {
111 ChordStroke cs = (ChordStroke)element;
112 str += is_eq_dev ? cs.chord : cs;
114 else if( element instanceof Lyrics ) {
115 str += element.toString();
120 public TickRange getRange() {
121 long start_tick_pos = -1;
122 long end_tick_pos = -1;
123 for( Object element : this ) {
124 if( ! (element instanceof ChordProgression.ChordStroke) )
126 ChordProgression.ChordStroke chord_stroke
127 = (ChordProgression.ChordStroke)element;
128 // 小節の先頭と末尾の tick を求める
129 if( start_tick_pos < 0 ) {
130 start_tick_pos = chord_stroke.tickRange.startTickPos;
132 end_tick_pos = chord_stroke.tickRange.end_tick_pos;
134 if( start_tick_pos < 0 || end_tick_pos < 0 ) {
137 return new TickRange( start_tick_pos, end_tick_pos );
139 public ChordStroke chordStrokeAt( long tick ) {
140 for( Object element : this ) {
141 if( ! (element instanceof ChordProgression.ChordStroke) )
143 ChordProgression.ChordStroke chord_stroke
144 = (ChordProgression.ChordStroke)element;
145 if( chord_stroke.tickRange.contains(tick) ) {
152 class Line extends Vector<Measure> {
153 public String toString() {
155 for( Measure measure : this ) str += measure + "|";
161 Vector<Line> lines = null;
163 private Long ticks_per_measure = null;
165 public Key getKey() { return key; }
166 public void setKey(Key key) { this.key = key; }
168 public String toString() {
170 if( key != null ) str += "Key: " + key + "\n";
171 for( Line line : lines ) str += line + "\n";
176 * デフォルトの設定でコード進行を構築します。
178 public ChordProgression() { }
180 * 指定された小節数、キー、拍子に合わせたコード進行を構築します。
181 * コード進行の内容は、ランダムに自動生成されます。
182 * @param measureLength 小節の長さ
183 * @param timeSignatureUpper 拍子の分子
185 public ChordProgression( int measureLength, int timeSignatureUpper ) {
186 int key_co5 = (int)(Math.random() * 12) - 5;
187 key = new Key(key_co5, Key.MajorMinor.MAJOR);
188 lines = new Vector<Line>();
189 Line line = new Line();
191 Chord chord, prev_chord = new Chord(new NoteSymbol(key_co5));
192 int co5_offset, prev_co5_offset;
194 for( int mp=0; mp<measureLength; mp++ ) {
195 is_end = (mp == 0 || mp == measureLength - 1); // 最初または最後の小節かを覚えておく
196 Measure measure = new Measure();
197 ChordStroke lastChordStroke = null;
198 for( int i=0; i<timeSignatureUpper; i++ ) {
200 i % 4 == 2 && Math.random() < 0.8
202 i % 2 != 0 && Math.random() < 0.9
205 lastChordStroke.beat_length++;
208 chord = new Chord(new NoteSymbol(key_co5));
210 prev_co5_offset = prev_chord.rootNoteSymbol().toCo5() - key_co5;
213 // 最初または最後の小節は常にトニックにする。
214 // 完全五度ずつ下がる進行を基本としつつ、時々そうでない進行も出現するようにする。
215 // サブドミナントを超えるとスケールを外れるので、超えそうになったらランダムに決め直す。
218 co5_offset = prev_co5_offset - 1;
219 if( co5_offset < -1 || (prev_co5_offset < 5 && r < 0.5) ) {
221 // 長7度がルートとなるコードの出現確率を半減させながらコードを決める
223 // なお、前回と同じコードは使わないようにする。
225 co5_offset = (int)(Math.random() * 13) % 7 - 1;
226 } while( co5_offset == prev_co5_offset );
228 int co5RootNote = key_co5 + co5_offset;
229 chord.setRoot(new NoteSymbol(co5RootNote));
230 chord.setBass(new NoteSymbol(co5RootNote));
232 switch( co5_offset ) {
233 // ルート音ごとに、7th などの付加や、メジャーマイナー反転を行う確率を決める
235 if( Math.random() < 0.5 ) {
237 chord.set(Chord.Interval.MINOR);
238 chord.set(Chord.Interval.FLAT5);
240 if( Math.random() < 0.8 )
241 chord.set(Chord.Interval.SEVENTH);
243 case 4: // Secondary dominant (III)
244 if( prev_co5_offset == 5 ) {
245 // ルートが長7度→長3度の進行のとき、反転確率を上げる。
246 // (ハ長調でいう Bm7-5 の次に E7 を出現しやすくする)
247 if( Math.random() < 0.2 ) chord.set(Chord.Interval.MINOR);
250 if( Math.random() < 0.8 ) chord.set(Chord.Interval.MINOR);
252 if( Math.random() < 0.7 ) chord.set(Chord.Interval.SEVENTH);
255 if( Math.random() < 0.8 ) chord.set(Chord.Interval.MINOR);
256 if( Math.random() < 0.7 ) chord.set(Chord.Interval.SEVENTH);
259 if( Math.random() < 0.8 ) chord.set(Chord.Interval.MINOR);
260 if( Math.random() < 0.7 ) chord.set(Chord.Interval.SEVENTH);
262 case 1: // Dominant (V)
263 if( Math.random() < 0.1 ) chord.set(Chord.Interval.MINOR);
264 if( Math.random() < 0.3 ) chord.set(Chord.Interval.SEVENTH);
265 if( Math.random() < 0.2 ) chord.set(Chord.Interval.NINTH);
267 case 0: // Tonic(ここでマイナーで終わるとさみしいので setMinorThird() はしない)
268 if( Math.random() < 0.2 ) chord.set(Chord.Interval.MAJOR_SEVENTH);
269 if( Math.random() < 0.2 ) chord.set(Chord.Interval.NINTH);
271 case -1: // Sub-dominant (IV)
272 if( Math.random() < 0.1 ) {
273 chord.set(Chord.Interval.MINOR);
274 if( Math.random() < 0.3 ) chord.set(Chord.Interval.SEVENTH);
277 if( Math.random() < 0.2 ) chord.set(Chord.Interval.MAJOR_SEVENTH);
278 if( Math.random() < 0.2 ) chord.set(Chord.Interval.NINTH);
281 measure.add( lastChordStroke = new ChordStroke(chord) );
285 if( (mp+1) % 8 == 0 ) { // 8小節おきに改行
290 if( line.size() > 0 ) lines.add(line);
293 public ChordProgression( String source_text ) {
294 if( source_text == null ) return;
297 String[] linesSrc, measuresSrc, elementsSrc;
298 Chord lastChord = null;
299 String keyHeaderRegex = "^Key(\\s*):(\\s*)";
300 String keyValueRegex = "[A-G]+.*$";
302 // キーであるかどうか見分けるためのパターン
303 Pattern keyMatchPattern = Pattern.compile(
304 keyHeaderRegex + keyValueRegex,
305 Pattern.CASE_INSENSITIVE
307 // キーのヘッダーを取り除くためのパターン
308 Pattern keyReplPattern = Pattern.compile(
309 keyHeaderRegex, Pattern.CASE_INSENSITIVE
312 linesSrc = source_text.split("[\r\n]+");
313 lines = new Vector<Line>();
314 for( String line_src : linesSrc ) {
315 measuresSrc = line_src.split("\\|");
316 if( measuresSrc.length > 0 ) {
317 String keyString = measuresSrc[0].trim();
318 if( keyMatchPattern.matcher(keyString).matches() ) {
320 key = new Key(keyReplPattern.matcher(keyString).replaceFirst(""));
322 } catch( Exception e ) {
328 for( String measureSrc : measuresSrc ) {
329 elementsSrc = measureSrc.split("[ \t]+");
330 measure = new Measure();
331 for( String elementSrc : elementsSrc ) {
332 if( elementSrc.isEmpty() ) continue;
333 if( elementSrc.equals("%") ) {
334 if( measure.addBeat() == 0 ) {
335 measure.add( new ChordStroke(lastChord) );
340 measure.add(new ChordStroke(lastChord = new Chord(elementSrc)));
341 } catch( IllegalArgumentException ex ) {
342 measure.add( new Lyrics(elementSrc) );
352 public void toggleKeyMajorMinor() { key = key.relativeKey(); }
355 public void transpose(int chromaticOffset) {
356 for( Line line : lines ) {
357 for( Measure measure : line ) {
358 for( int i=0; i<measure.size(); i++ ) {
359 Object element = measure.get(i);
360 if( element instanceof ChordStroke ) {
361 ChordStroke cs = (ChordStroke)element;
362 Chord newChord = cs.chord.clone();
364 // キーが未設定のときは、最初のコードから推測して設定
365 if( key == null ) key = new Key(newChord);
367 newChord.transpose( chromaticOffset, key );
368 measure.set(i, new ChordStroke(newChord, cs.beat_length));
373 key = key.transposedKey(chromaticOffset);
376 public void toggleEnharmonically() {
377 if( key == null ) return;
378 int original_key_co5 = key.toCo5();
380 if( original_key_co5 > 4 ) {
381 co5Offset = -Music.SEMITONES_PER_OCTAVE;
383 else if( original_key_co5 < -4 ) {
384 co5Offset = Music.SEMITONES_PER_OCTAVE;
389 key = key.enharmonicKey();
390 for( Line line : lines ) {
391 for( Measure measure : line ) {
392 for( int i=0; i<measure.size(); i++ ) {
393 Object element = measure.get(i);
394 if( element instanceof ChordStroke ) {
395 ChordStroke cs = (ChordStroke)element;
396 Chord newChord = cs.chord.clone();
397 newChord.setRoot(new NoteSymbol(newChord.rootNoteSymbol().toCo5() + co5Offset));
398 newChord.setBass(new NoteSymbol(newChord.bassNoteSymbol().toCo5() + co5Offset));
399 measure.set( i, new ChordStroke( newChord, cs.beat_length ) );
405 // コード進行の中に時間軸(MIDI tick)を書き込む
407 public void setTickPositions( FirstTrackSpec first_track ) {
408 ticks_per_measure = first_track.ticksPerMeasure;
409 TickRange tick_range = new TickRange(
410 first_track.preMeasures * ticks_per_measure
412 for( Line line : lines ) { // 行単位の処理
413 for( Measure measure : line ) { // 小節単位の処理
414 int n_beats = measure.numberOfBeats();
415 if( n_beats == 0 ) continue;
416 long tpb = measure.ticks_per_beat = ticks_per_measure / n_beats ;
417 for( Object element : measure ) {
418 if( element instanceof Lyrics ) {
419 ((Lyrics)element).startTickPos = tick_range.startTickPos;
422 else if( element instanceof ChordStroke ) {
423 ChordStroke chord_stroke = (ChordStroke)element;
424 tick_range.moveForward( tpb * chord_stroke.beat_length );
425 chord_stroke.tickRange = tick_range.clone();
432 public void setChordSymbolTextTo( AbstractTrackSpec ts ) {
433 for( Line line : lines ) {
434 for( Measure measure : line ) {
435 if( measure.ticks_per_beat == null ) continue;
436 for( Object element : measure ) {
437 if( element instanceof ChordStroke ) {
438 ts.addStringTo( 0x01, (ChordStroke)element );
445 public void setLyricsTo( AbstractTrackSpec ts ) {
446 for( Line line : lines ) {
447 for( Measure measure : line ) {
448 if( measure.ticks_per_beat == null ) continue;
449 for( Object element : measure ) {
450 if( element instanceof Lyrics ) {
451 ts.addStringTo( 0x05, (Lyrics)element );
458 * コード進行をもとに MIDI シーケンスを生成します。
461 public Sequence toMidiSequence() {
462 return toMidiSequence(48);
466 * コード進行をもとに MIDI シーケンスを生成します。
469 public Sequence toMidiSequence(int ppq) {
471 // PPQ = Pulse Per Quarter (TPQN = Tick Per Quearter Note)
473 return toMidiSequence( ppq, 0, 0, null, null );
476 * 小節数、トラック仕様、コード進行をもとに MIDI シーケンスを生成します。
477 * @param ppq 分解能(pulse per quarter)
478 * @param startMeasure 開始小節位置
479 * @param endMeasure 終了小節位置
480 * @param firstTrack 最初のトラックの仕様
481 * @param trackSpecs 残りのトラックの仕様
484 public Sequence toMidiSequence(
485 int ppq, int startMeasure, int endMeasure,
486 FirstTrackSpec firstTrack,
487 Vector<AbstractNoteTrackSpec> trackSpecs
491 seq = new Sequence(Sequence.PPQ, ppq);
492 } catch ( InvalidMidiDataException e ) {
497 if( firstTrack == null ) {
498 firstTrack = new FirstTrackSpec();
500 firstTrack.key = this.key;
501 firstTrack.createTrack( seq, startMeasure, endMeasure );
504 if( lines == null || trackSpecs == null ) return seq;
506 // コード進行の中に時間軸(MIDI tick)を書き込む
507 setTickPositions(firstTrack);
510 setChordSymbolTextTo(firstTrack);
511 setLyricsTo(firstTrack);
514 for( AbstractNoteTrackSpec ts : trackSpecs ) {
515 ts.createTrack(seq, firstTrack);
516 if( ts instanceof DrumTrackSpec ) {
517 ((DrumTrackSpec)ts).addDrums(this);
520 ((MelodyTrackSpec)ts).addChords(this);