1 package camidion.chordhelper.music;
5 * 調(キー)を表すクラスです。値は不変です。
7 * <p>内部的には次の値を持っています。</p>
9 * <li>五度圏インデックス値。これは調号の♯の数(♭の数は負数)と同じです。</li>
10 * <li>メジャー/マイナーの区別、区別なしの3値({@link MajorMinor}で定義)</li>
12 * <p>これらの値はMIDIのメタメッセージにある調号のパラメータに対応します。
19 public static final int MAX_SHARPS_OR_FLATS = 7;
23 public enum MajorMinor {
29 public MajorMinor opposite() { return MAJOR; }
36 public boolean includes(MajorMinor mm) { return true; }
43 public MajorMinor opposite() { return MINOR; }
46 private MajorMinor(int index) { this.index = index; }
47 /** インデックス値(マイナー:-1、区別なし:0、メジャー:1)を返します。 */
48 public int index() { return index; }
50 public MajorMinor opposite() { return this; }
51 /** この値が、引数で指定されたメジャー/マイナーを含んだ意味であるときにtrueを返します。 */
52 public boolean includes(MajorMinor mm) { return equals(mm); }
61 private MajorMinor majorMinor = MajorMinor.MAJOR_OR_MINOR;
63 * 調号が空(C/Am ハ長調またはイ短調)で、メジャー・マイナーの区別のない調を構築します。
67 * 指定の五度圏インデックス値を持つ、メジャー・マイナーの区別のない調を構築します。
69 * @param co5 五度圏インデックス値
71 public Key(int co5) { this.co5 = co5; normalize(); }
73 * 指定の五度圏インデックス値を持つ、メジャー・マイナーの区別を指定した調を構築します。
75 * @param co5 五度圏インデックス値
76 * @param majorMinor メジャー・マイナーの区別
78 public Key(int co5, MajorMinor majorMinor) {
80 this.majorMinor = majorMinor;
84 * MIDIの調データ(メタメッセージ2byteの配列)から調を構築します。
86 * <li>長さ0の配列が与えられた場合は{@link #Key() 引数のないコンストラクタ}と同じ動作になります。</li>
87 * <li>メジャー・マイナーの区別を表す有効な値が見つからなかった場合、区別なしとして構築されます。</li>
90 * @param midiData MIDIの調データ
92 public Key(byte midiData[]) {
93 if( midiData.length == 0 ) return;
95 if( midiData.length > 1 ) {
97 case 0 : majorMinor = MajorMinor.MAJOR; break;
98 case 1 : majorMinor = MajorMinor.MINOR; break;
104 * C、Am のような文字列から調を構築します。
105 * mがあればマイナー、なければメジャーとなります(区別のない状態にはなりません)。
107 * @param keySymbol 調を表す文字列
108 * @throw IllegalArgumentException 引数が空文字列の場合、または音名で始まっていない場合
110 public Key(String keySymbol) throws IllegalArgumentException {
111 co5 = NoteSymbol.co5OfSymbol(keySymbol);
112 if( keySymbol.matches(".*m") ) { majorMinor = MajorMinor.MINOR; co5 -= 3; }
113 else majorMinor = MajorMinor.MAJOR;
117 * 指定されたコードと同名、または最も近い調を構築します。
119 * <li>コード構成音に短3度があればマイナー、なければメジャーとなります(区別なしになることはありません)。</li>
120 * <li>調として存在しないコード(例:A♯)が来た場合、存在する異名同音の調(例:B♭)に置き換えられます。</li>
122 * @param chord コード(和音)
124 public Key(Chord chord) {
125 co5 = chord.rootNoteSymbol().toCo5();
126 if( chord.isSet(Chord.Interval.MINOR) ) { majorMinor = MajorMinor.MINOR; co5 -= 3; }
127 else majorMinor = MajorMinor.MAJOR;
132 * 調が7♭~7♯の範囲に入っていない場合、
135 private void normalize() {
136 if( co5 >= -MAX_SHARPS_OR_FLATS && co5 <= MAX_SHARPS_OR_FLATS ) return;
137 if( (co5 = Music.mod12(co5)) > 6 ) co5 -= Music.SEMITONES_PER_OCTAVE;
143 public int toCo5() { return co5; }
146 * @return メジャー・マイナーの区別
148 public MajorMinor majorMinor() { return majorMinor; }
150 public boolean equals(Object anObject) {
151 if( this == anObject ) return true;
152 if( anObject instanceof Key ) {
153 Key another = (Key) anObject;
154 return co5 == another.toCo5() && majorMinor == another.majorMinor();
159 public int hashCode() { return 0x4000 * majorMinor.index() + co5 ; }
161 * この調の文字列表現を C、Am のような形式で返します。
162 * メジャー・マイナーの区別がない場合、調号を先頭に付加して返します。
166 public String toString() {
167 String s = toStringIn(NoteSymbol.Language.SYMBOL);
168 if(majorMinor == MajorMinor.MAJOR_OR_MINOR) s = signature() + " : " + s;
172 * この調の文字列表現を、指定された形式で返します。
175 public String toStringIn(NoteSymbol.Language language) {
176 return language.stringOf(this);
180 * 正規化された状態において最大2文字になるよう調整されます。
184 public String signature() {
190 case -2: return "bb";
192 if( co5 >= 3 && co5 <= 7 ) return co5 + "#" ;
193 else if( co5 <= -3 && co5 >= -7 ) return (-co5) + "b" ;
201 public String signatureDescription() {
203 case 0: return "no sharps or flats";
204 case 1: return "1 sharp";
205 case -1: return "1 flat";
206 default: return co5 < 0 ? (-co5) + " flats" : co5 + " sharps" ;
210 * MIDIの調データ(メタメッセージ2byte)を生成して返します。
213 public byte[] getBytes() {
214 return new byte[] {(byte) co5, (byte) ((majorMinor == MajorMinor.MINOR) ? 1 : 0)};
218 * @return 相対ドの音階(0~11)
220 public int relativeDo() { return NoteSymbol.majorCo5ToNoteNumber(co5); }
222 * この調のルート音を表すノート番号(オクターブ抜き)を返します。
224 * マイナーキーの場合は相対ラの音階です。
226 * @return キーのルート音(0~11)
228 public int rootNoteNumber() {
229 int n = relativeDo();
230 return majorMinor==MajorMinor.MINOR ? Music.mod12(n-3) : n;
233 * 指定されたノート番号の音が、この調のスケールの構成音か調べます。
234 * メジャーキーの場合はメジャースケール、
235 * マイナーキーの場合はナチュラルマイナースケールとして判断されます。
237 * @param noteNumber ノート番号
238 * @return 指定されたノート番号がこのキーのスケールの構成音ならtrue
240 public boolean isOnScale(int noteNumber) { return Music.isOnScale(noteNumber, co5); }
243 * これは元の調と同じ調号を持つ、メジャーとマイナーが異なる調です。
244 * メジャーとマイナーの区別が不明な場合、この調自身を返します。
247 public Key relativeKey() {
248 MajorMinor mmo = majorMinor.opposite();
249 return mmo.equals(majorMinor) ? this : new Key(co5, mmo);
253 * これは元の調とルート音が同じで、メジャーとマイナーが異なる調です。
254 * メジャーとマイナーの区別が不明な場合、この調自身を返します。
258 public Key parallelKey() {
259 MajorMinor mmo = majorMinor.opposite();
260 return mmo.equals(majorMinor) ? this : new Key(co5 - 3 * majorMinor.index(), mmo);
263 * この調に異名同音の調があれば、それを返します。
264 * <p>例えば、♭5個(D♭メジャー)の場合、異名同音の調は♯7個(C♯メジャー)となります。
265 * 異名同音の調が存在しない調(4♯~4♭)に対してこのメソッドを呼び出した場合、
270 public Key enharmonicKey() {
272 if( newCo5 > 4 ) newCo5 -= 12; else if( newCo5 < -4 ) newCo5 += 12;
273 return newCo5 == co5 ? this : new Key(newCo5, majorMinor);
276 * 指定された半音オフセット値だけ移調した調を返します。
277 * 半音オフセット値が 0 の場合、この調自身を返します。
279 * @param chromaticOffset 半音オフセット値
282 public Key transposedKey(int chromaticOffset) {
283 return chromaticOffset == 0 ? this : new Key(Music.transposeCo5(co5, chromaticOffset), majorMinor);
287 * @return 五度圏で真裏にあたる調
289 public Key createOppositeKey() {
290 return new Key(Music.oppositeCo5(co5), majorMinor);