OSDN Git Service

リファクタリング(Music.Keyクラスの値を不変にし、キー選択フォームなどを修正)
[midichordhelper/MIDIChordHelper.git] / src / camidion / chordhelper / music / ChordProgression.java
1 package camidion.chordhelper.music;
2
3 import java.util.Vector;
4 import java.util.regex.Pattern;
5
6 import javax.sound.midi.InvalidMidiDataException;
7 import javax.sound.midi.Sequence;
8
9 /**
10  * Chord Progression - コード進行のクラス
11  */
12 public class ChordProgression {
13
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;
18                 }
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;
22                 }
23                 protected TickRange clone() {
24                         return new TickRange( startTickPos, end_tick_pos );
25                 }
26                 public void moveForward() {
27                         startTickPos = end_tick_pos;
28                 }
29                 public void moveForward( long duration ) {
30                         startTickPos = end_tick_pos;
31                         end_tick_pos += duration;
32                 }
33                 public long duration() {
34                         return end_tick_pos - startTickPos;
35                 }
36                 public boolean contains( long tick ) {
37                         return ( tick >= startTickPos && tick < end_tick_pos );
38                 }
39         }
40
41         class ChordStroke {
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) {
45                         this.chord = chord;
46                         this.beat_length = beat_length;
47                 }
48                 public String toString() {
49                         String str = chord.toString();
50                         for( int i=2; i <= beat_length; i++ ) str += " %";
51                         return str;
52                 }
53         }
54
55         // 時間位置付き歌詞
56         public class Lyrics {
57                 String text = null;
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;
62                 }
63                 public String toString() { return text; }
64         }
65
66         class Measure extends Vector<Object> {
67                 Long ticks_per_beat = null;
68                 public int numberOfBeats() {
69                         int n = 0;
70                         for( Object obj : this ) {
71                                 if( obj instanceof ChordStroke ) {
72                                         n += ((ChordStroke)obj).beat_length;
73                                 }
74                         }
75                         return n;
76                 }
77                 // 小節内のコードストロークが時間的に等間隔かどうか調べる。
78                 // もし等間隔の場合、テキスト出力時に % をつける必要がなくなる。
79                 public boolean isEquallyDivided() {
80                         int l, l_prev = 0;
81                         for( Object obj : this ) {
82                                 if( obj instanceof ChordStroke ) {
83                                         l = ((ChordStroke)obj).beat_length;
84                                         if( l_prev > 0 && l_prev != l ) {
85                                                 return false;
86                                         }
87                                         l_prev = l;
88                                 }
89                         }
90                         return true;
91                 }
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;
98                                 }
99                         }
100                         if( last_chord_stroke == null ) {
101                                 return 0;
102                         }
103                         return last_chord_stroke.beat_length += num_beats;
104                 }
105                 public String toString() {
106                         String str = "";
107                         boolean is_eq_dev = isEquallyDivided();
108                         for( Object element : this ) {
109                                 str += " ";
110                                 if( element instanceof ChordStroke ) {
111                                         ChordStroke cs = (ChordStroke)element;
112                                         str += is_eq_dev ? cs.chord : cs;
113                                 }
114                                 else if( element instanceof Lyrics ) {
115                                         str += element.toString();
116                                 }
117                         }
118                         return str;
119                 }
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) )
125                                         continue;
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;
131                                 }
132                                 end_tick_pos = chord_stroke.tickRange.end_tick_pos;
133                         }
134                         if( start_tick_pos < 0 || end_tick_pos < 0 ) {
135                                 return null;
136                         }
137                         return new TickRange( start_tick_pos, end_tick_pos );
138                 }
139                 public ChordStroke chordStrokeAt( long tick ) {
140                         for( Object element : this ) {
141                                 if( ! (element instanceof ChordProgression.ChordStroke) )
142                                         continue;
143                                 ChordProgression.ChordStroke chord_stroke
144                                 = (ChordProgression.ChordStroke)element;
145                                 if( chord_stroke.tickRange.contains(tick) ) {
146                                         return chord_stroke;
147                                 }
148                         }
149                         return null;
150                 }
151         }
152         class Line extends Vector<Measure> {
153                 public String toString() {
154                         String str = "";
155                         for( Measure measure : this ) str += measure + "|";
156                         return str;
157                 }
158         }
159
160         // 内部変数
161         Vector<Line> lines = null;
162         Key key = null;
163         private Long ticks_per_measure = null;
164
165         public Key getKey() { return key; }
166         public void setKey(Key key) { this.key = key; }
167
168         public String toString() {
169                 String str = "";
170                 if( key != null ) str += "Key: " + key + "\n";
171                 for( Line line : lines ) str += line + "\n";
172                 return str;
173         }
174
175         /**
176          * デフォルトの設定でコード進行を構築します。
177          */
178         public ChordProgression() { }
179         /**
180          * 指定された小節数、キー、拍子に合わせたコード進行を構築します。
181          * コード進行の内容は、ランダムに自動生成されます。
182          * @param measureLength 小節の長さ
183          * @param timeSignatureUpper 拍子の分子
184          */
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();
190                 boolean is_end;
191                 Chord chord, prev_chord = new Chord(new NoteSymbol(key_co5));
192                 int co5_offset, prev_co5_offset;
193                 double r;
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++ ) {
199                                 if(
200                                         i % 4 == 2 && Math.random() < 0.8
201                                         ||
202                                         i % 2 != 0 && Math.random() < 0.9
203                                 ){
204                                         // もう一拍延長
205                                         lastChordStroke.beat_length++;
206                                         continue;
207                                 }
208                                 chord = new Chord(new NoteSymbol(key_co5));
209                                 co5_offset = 0;
210                                 prev_co5_offset = prev_chord.rootNoteSymbol().toCo5() - key_co5;
211                                 if( ! is_end ) {
212                                         //
213                                         // 最初または最後の小節は常にトニックにする。
214                                         // 完全五度ずつ下がる進行を基本としつつ、時々そうでない進行も出現するようにする。
215                                         // サブドミナントを超えるとスケールを外れるので、超えそうになったらランダムに決め直す。
216                                         //
217                                         r = Math.random();
218                                         co5_offset = prev_co5_offset - 1;
219                                         if( co5_offset < -1 || (prev_co5_offset < 5 && r < 0.5) ) {
220                                                 //
221                                                 // 長7度がルートとなるコードの出現確率を半減させながらコードを決める
222                                                 // (余りが6のときだけが長7度)
223                                                 // なお、前回と同じコードは使わないようにする。
224                                                 do {
225                                                         co5_offset = (int)(Math.random() * 13) % 7 - 1;
226                                                 } while( co5_offset == prev_co5_offset );
227                                         }
228                                         int co5RootNote = key_co5 + co5_offset;
229                                         chord.setRoot(new NoteSymbol(co5RootNote));
230                                         chord.setBass(new NoteSymbol(co5RootNote));
231                                 }
232                                 switch( co5_offset ) {
233                                 // ルート音ごとに、7th などの付加や、メジャーマイナー反転を行う確率を決める
234                                 case 5: // VII
235                                         if( Math.random() < 0.5 ) {
236                                                 // m7-5
237                                                 chord.set(Chord.Interval.MINOR);
238                                                 chord.set(Chord.Interval.FLAT5);
239                                         }
240                                         if( Math.random() < 0.8 )
241                                                 chord.set(Chord.Interval.SEVENTH);
242                                         break;
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);
248                                         }
249                                         else {
250                                                 if( Math.random() < 0.8 ) chord.set(Chord.Interval.MINOR);
251                                         }
252                                         if( Math.random() < 0.7 ) chord.set(Chord.Interval.SEVENTH);
253                                         break;
254                                 case 3: // VI
255                                         if( Math.random() < 0.8 ) chord.set(Chord.Interval.MINOR);
256                                         if( Math.random() < 0.7 ) chord.set(Chord.Interval.SEVENTH);
257                                         break;
258                                 case 2: // II
259                                         if( Math.random() < 0.8 ) chord.set(Chord.Interval.MINOR);
260                                         if( Math.random() < 0.7 ) chord.set(Chord.Interval.SEVENTH);
261                                         break;
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);
266                                         break;
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);
270                                         break;
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);
275                                         }
276                                         else
277                                                 if( Math.random() < 0.2 ) chord.set(Chord.Interval.MAJOR_SEVENTH);
278                                         if( Math.random() < 0.2 ) chord.set(Chord.Interval.NINTH);
279                                         break;
280                                 }
281                                 measure.add( lastChordStroke = new ChordStroke(chord) );
282                                 prev_chord = chord;
283                         }
284                         line.add(measure);
285                         if( (mp+1) % 8 == 0 ) { // 8小節おきに改行
286                                 lines.add(line);
287                                 line = new Line();
288                         }
289                 }
290                 if( line.size() > 0 ) lines.add(line);
291         }
292         // テキストからコード進行を生成
293         public ChordProgression( String source_text ) {
294                 if( source_text == null ) return;
295                 Measure measure;
296                 Line line;
297                 String[] linesSrc, measuresSrc, elementsSrc;
298                 Chord lastChord = null;
299                 String keyHeaderRegex = "^Key(\\s*):(\\s*)";
300                 String keyValueRegex = "[A-G]+.*$";
301                 //
302                 // キーであるかどうか見分けるためのパターン
303                 Pattern keyMatchPattern = Pattern.compile(
304                         keyHeaderRegex + keyValueRegex,
305                         Pattern.CASE_INSENSITIVE
306                 );
307                 // キーのヘッダーを取り除くためのパターン
308                 Pattern keyReplPattern = Pattern.compile(
309                         keyHeaderRegex, Pattern.CASE_INSENSITIVE
310                 );
311                 //
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() ) {
319                                         try {
320                                                 key = new Key(keyReplPattern.matcher(keyString).replaceFirst(""));
321                                                 continue;
322                                         } catch( Exception e ) {
323                                                 e.printStackTrace();
324                                         }
325                                 }
326                         }
327                         line = new Line();
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) );
336                                                 }
337                                                 continue;
338                                         }
339                                         try {
340                                                 measure.add(new ChordStroke(lastChord = new Chord(elementSrc)));
341                                         } catch( IllegalArgumentException ex ) {
342                                                 measure.add( new Lyrics(elementSrc) );
343                                         }
344                                 }
345                                 line.add(measure);
346                         }
347                         lines.add(line);
348                 }
349         }
350
351         // Major/minor 切り替え
352         public void toggleKeyMajorMinor() { key = key.relativeKey(); }
353
354         // コード進行の移調
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();
363                                                 //
364                                                 // キーが未設定のときは、最初のコードから推測して設定
365                                                 if( key == null ) key = new Key(newChord);
366                                                 //
367                                                 newChord.transpose( chromaticOffset, key );
368                                                 measure.set(i, new ChordStroke(newChord, cs.beat_length));
369                                         }
370                                 }
371                         }
372                 }
373                 key = key.transposedKey(chromaticOffset);
374         }
375         // 異名同音の♭と#を切り替える
376         public void toggleEnharmonically() {
377                 if( key == null ) return;
378                 int original_key_co5 = key.toCo5();
379                 int co5Offset = 0;
380                 if( original_key_co5 > 4 ) {
381                         co5Offset = -Music.SEMITONES_PER_OCTAVE;
382                 }
383                 else if( original_key_co5 < -4 ) {
384                         co5Offset = Music.SEMITONES_PER_OCTAVE;
385                 }
386                 else {
387                         return;
388                 }
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 ) );
400                                         }
401                                 }
402                         }
403                 }
404         }
405         // コード進行の中に時間軸(MIDI tick)を書き込む
406         //
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
411                                 );
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;
420                                                 continue;
421                                         }
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();
426                                         }
427                                 }
428                         }
429                 }
430         }
431         // コード文字列の書き込み
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 );
439                                         }
440                                 }
441                         }
442                 }
443         }
444         // 歌詞の書き込み
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 );
452                                         }
453                                 }
454                         }
455                 }
456         }
457         /**
458          * コード進行をもとに MIDI シーケンスを生成します。
459          * @return MIDIシーケンス
460          */
461         public Sequence toMidiSequence() {
462                 return toMidiSequence(48);
463         }
464         /**
465          * 指定のタイミング解像度で、
466          * コード進行をもとに MIDI シーケンスを生成します。
467          * @return MIDIシーケンス
468          */
469         public Sequence toMidiSequence(int ppq) {
470                 //
471                 // PPQ = Pulse Per Quarter (TPQN = Tick Per Quearter Note)
472                 //
473                 return toMidiSequence( ppq, 0, 0, null, null );
474         }
475         /**
476          * 小節数、トラック仕様、コード進行をもとに MIDI シーケンスを生成します。
477          * @param ppq 分解能(pulse per quarter)
478          * @param startMeasure 開始小節位置
479          * @param endMeasure 終了小節位置
480          * @param firstTrack 最初のトラックの仕様
481          * @param trackSpecs 残りのトラックの仕様
482          * @return MIDIシーケンス
483          */
484         public Sequence toMidiSequence(
485                 int ppq, int startMeasure, int endMeasure,
486                 FirstTrackSpec firstTrack,
487                 Vector<AbstractNoteTrackSpec> trackSpecs
488         ) {
489                 Sequence seq;
490                 try {
491                         seq = new Sequence(Sequence.PPQ, ppq);
492                 } catch ( InvalidMidiDataException e ) {
493                         e.printStackTrace();
494                         return null;
495                 }
496                 // マスタートラックの生成
497                 if( firstTrack == null ) {
498                         firstTrack = new FirstTrackSpec();
499                 }
500                 firstTrack.key = this.key;
501                 firstTrack.createTrack( seq, startMeasure, endMeasure );
502                 //
503                 // 中身がなければここで終了
504                 if( lines == null || trackSpecs == null ) return seq;
505                 //
506                 // コード進行の中に時間軸(MIDI tick)を書き込む
507                 setTickPositions(firstTrack);
508                 //
509                 // コードのテキストと歌詞を書き込む
510                 setChordSymbolTextTo(firstTrack);
511                 setLyricsTo(firstTrack);
512                 //
513                 // 残りのトラックを生成
514                 for( AbstractNoteTrackSpec ts : trackSpecs ) {
515                         ts.createTrack(seq, firstTrack);
516                         if( ts instanceof DrumTrackSpec ) {
517                                 ((DrumTrackSpec)ts).addDrums(this);
518                         }
519                         else {
520                                 ((MelodyTrackSpec)ts).addChords(this);
521                         }
522                 }
523                 return seq;
524         }
525 }