package com.xenoage.zong.core.music;
import lombok.Data;
import com.xenoage.utils.annotations.Const;
/**
* Pitch is represented as a combination of the step of the
* diatonic scale, the chromatic alteration, and the octave.
* The step element uses the numbers 0 (C) to 6 (B).
* The alter element represents chromatic alteration in
* number of semitones (e.g., -1 for flat, 1 for sharp).
* The octave element is represented
* by the numbers 0 to 9, where 4 indicates the octave
* started by middle C.
* (Parts copied from the MusicXML specification)
*
* @author Andreas Wenger
*/
@Const @Data public final class Pitch
implements Comparable<Pitch> {
public static final byte C = 0;
public static final byte D = 1;
public static final byte E = 2;
public static final byte F = 3;
public static final byte G = 4;
public static final byte A = 5;
public static final byte B = 6;
private final byte step;
private final byte alter;
private final byte octave;
private Pitch(byte step, byte alter, byte octave) {
if (step < 0 || step > 6)
throw new IllegalArgumentException("Invalid step: " + step);
this.step = step;
this.alter = alter;
this.octave = octave;
}
private Pitch(char step, byte alter, byte octave) {
switch (step) {
case 'C':
this.step = 0;
break;
case 'D':
this.step = 1;
break;
case 'E':
this.step = 2;
break;
case 'F':
this.step = 3;
break;
case 'G':
this.step = 4;
break;
case 'A':
this.step = 5;
break;
case 'B':
this.step = 6;
break;
default:
throw new IllegalArgumentException("Unknown pitch step: " + step);
}
this.alter = alter;
this.octave = octave;
}
public static Pitch pi(int step, int alter, int octave) {
return new Pitch((byte) step, (byte) alter, (byte) octave);
}
public static Pitch pi(char step, int alter, int octave) {
return new Pitch(step, (byte) alter, (byte) octave);
}
public static Pitch pi(int step, int octave) {
return new Pitch((byte) step, (byte) 0, (byte) octave);
}
/**
* Gets a copy of this pitch without alter.
*/
public Pitch withoutAlter() {
return pi(step, (byte) 0, octave);
}
public char getStepAsChar() {
switch (step) {
case 0:
return 'C';
case 1:
return 'D';
case 2:
return 'E';
case 3:
return 'F';
case 4:
return 'G';
case 5:
return 'A';
case 6:
return 'B';
}
throw new IllegalStateException("Invalid step: " + step);
}
/**
* Returns this Pitch as a String in the
* format "(step,alter,octave)", e.g. "(1,0,4)".
*/
@Override public String toString() {
return "(" + step + "," + alter + "," + octave + ")";
}
@Override public int compareTo(Pitch pitch) {
if (pitch == null)
return 0;
int thisval = this.octave * 12 + getStepSemitones(this.step) + this.alter;
int pitchval = pitch.octave * 12 + getStepSemitones(pitch.step) + pitch.alter;
if (thisval > pitchval)
return 1;
else if (thisval < pitchval)
return -1;
else
return 0;
}
/**
* Like {@link #compareTo(Pitch)}, but ignores the alter component.
* E.g. a E# has a lower notation position than Fb, although it sounds
* higher. This method regards only the notation position.
*/
public int compareToNotation(Pitch pitch) {
if (pitch == null)
return 0;
int thisval = this.octave * 12 + getStepSemitones(this.step);
int pitchval = pitch.octave * 12 + getStepSemitones(pitch.step);
if (thisval > pitchval)
return 1;
else if (thisval < pitchval)
return -1;
else
return 0;
}
private int getStepSemitones(int step) {
switch (step) {
case 0:
return 0;
case 1:
return 2;
case 2:
return 4;
case 3:
return 5;
case 4:
return 7;
case 5:
return 9;
case 6:
return 11;
default:
return 0;
}
}
}