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() {
353 key = key.createRelativeKey();
357 public void transpose(int chromaticOffset) {
358 for( Line line : lines ) {
359 for( Measure measure : line ) {
360 for( int i=0; i<measure.size(); i++ ) {
361 Object element = measure.get(i);
362 if( element instanceof ChordStroke ) {
363 ChordStroke cs = (ChordStroke)element;
364 Chord newChord = cs.chord.clone();
366 // キーが未設定のときは、最初のコードから推測して設定
367 if( key == null ) key = new Key(newChord);
369 newChord.transpose( chromaticOffset, key );
370 measure.set(i, new ChordStroke(newChord, cs.beat_length));
375 key.transpose(chromaticOffset);
378 public void toggleEnharmonically() {
379 if( key == null ) return;
380 int original_key_co5 = key.toCo5();
382 if( original_key_co5 > 4 ) {
383 co5Offset = -Music.SEMITONES_PER_OCTAVE;
385 else if( original_key_co5 < -4 ) {
386 co5Offset = Music.SEMITONES_PER_OCTAVE;
391 key.toggleEnharmonically();
392 for( Line line : lines ) {
393 for( Measure measure : line ) {
394 for( int i=0; i<measure.size(); i++ ) {
395 Object element = measure.get(i);
396 if( element instanceof ChordStroke ) {
397 ChordStroke cs = (ChordStroke)element;
398 Chord newChord = cs.chord.clone();
399 newChord.setRoot(new NoteSymbol(newChord.rootNoteSymbol().toCo5() + co5Offset));
400 newChord.setBass(new NoteSymbol(newChord.bassNoteSymbol().toCo5() + co5Offset));
401 measure.set( i, new ChordStroke( newChord, cs.beat_length ) );
407 // コード進行の中に時間軸(MIDI tick)を書き込む
409 public void setTickPositions( FirstTrackSpec first_track ) {
410 ticks_per_measure = first_track.ticksPerMeasure;
411 TickRange tick_range = new TickRange(
412 first_track.preMeasures * ticks_per_measure
414 for( Line line : lines ) { // 行単位の処理
415 for( Measure measure : line ) { // 小節単位の処理
416 int n_beats = measure.numberOfBeats();
417 if( n_beats == 0 ) continue;
418 long tpb = measure.ticks_per_beat = ticks_per_measure / n_beats ;
419 for( Object element : measure ) {
420 if( element instanceof Lyrics ) {
421 ((Lyrics)element).startTickPos = tick_range.startTickPos;
424 else if( element instanceof ChordStroke ) {
425 ChordStroke chord_stroke = (ChordStroke)element;
426 tick_range.moveForward( tpb * chord_stroke.beat_length );
427 chord_stroke.tickRange = tick_range.clone();
434 public void setChordSymbolTextTo( AbstractTrackSpec ts ) {
435 for( Line line : lines ) {
436 for( Measure measure : line ) {
437 if( measure.ticks_per_beat == null ) continue;
438 for( Object element : measure ) {
439 if( element instanceof ChordStroke ) {
440 ts.addStringTo( 0x01, (ChordStroke)element );
447 public void setLyricsTo( AbstractTrackSpec ts ) {
448 for( Line line : lines ) {
449 for( Measure measure : line ) {
450 if( measure.ticks_per_beat == null ) continue;
451 for( Object element : measure ) {
452 if( element instanceof Lyrics ) {
453 ts.addStringTo( 0x05, (Lyrics)element );
460 * コード進行をもとに MIDI シーケンスを生成します。
463 public Sequence toMidiSequence() {
464 return toMidiSequence(48);
468 * コード進行をもとに MIDI シーケンスを生成します。
471 public Sequence toMidiSequence(int ppq) {
473 // PPQ = Pulse Per Quarter (TPQN = Tick Per Quearter Note)
475 return toMidiSequence( ppq, 0, 0, null, null );
478 * 小節数、トラック仕様、コード進行をもとに MIDI シーケンスを生成します。
479 * @param ppq 分解能(pulse per quarter)
480 * @param startMeasure 開始小節位置
481 * @param endMeasure 終了小節位置
482 * @param firstTrack 最初のトラックの仕様
483 * @param trackSpecs 残りのトラックの仕様
486 public Sequence toMidiSequence(
487 int ppq, int startMeasure, int endMeasure,
488 FirstTrackSpec firstTrack,
489 Vector<AbstractNoteTrackSpec> trackSpecs
493 seq = new Sequence(Sequence.PPQ, ppq);
494 } catch ( InvalidMidiDataException e ) {
499 if( firstTrack == null ) {
500 firstTrack = new FirstTrackSpec();
502 firstTrack.key = this.key;
503 firstTrack.createTrack( seq, startMeasure, endMeasure );
506 if( lines == null || trackSpecs == null ) return seq;
508 // コード進行の中に時間軸(MIDI tick)を書き込む
509 setTickPositions(firstTrack);
512 setChordSymbolTextTo(firstTrack);
513 setLyricsTo(firstTrack);
516 for( AbstractNoteTrackSpec ts : trackSpecs ) {
517 ts.createTrack(seq, firstTrack);
518 if( ts instanceof DrumTrackSpec ) {
519 ((DrumTrackSpec)ts).addDrums(this);
522 ((MelodyTrackSpec)ts).addChords(this);