我正在尝试创建一种算法,根据给定的比例和转置因子向上或向下转换音符。
当前算法:
public Note getNoteByScale(Scale scale, int transposeFactor) {
int newPitch = scale.getScaleIndex(this.pitch) + transposeFactor;
int newOctave = this.octave;
if (newPitch > 6) {
newPitch -= 7;
newOctave++;
}
if (newPitch < 0) {
newPitch += 7;
}
return Note.createNote(scale.getNotesInScale().get(newPitch),
newOctave, this.duration);
}
每个尺度(为此目的表示为主要尺度)有7个音符。例如,C-Major Scale有以下注释:
C, D, E, F, G, A, and B
如果您要使用上述算法的这个音阶来转换音符,例如,在C-Major音阶的5个八度的八度音符上的“B”音符,该算法将按预期工作并返回音符'C'(音阶的索引0)在一个八度音阶上。
然而,假设我们使用由笔记组成的D-Major Scale
D, E, F♯, G, A, B, and C♯
对于这个音阶,最后一个音符'C♯'应该被认为比音阶中的其他音符高八度。例如,如果我们使用上面的算法将音符'B'(索引6)转换为5的八度音程,那么算法实际上会在5的八度音程上给出音符'C♯',这是完全错误的:它应该在6的八度。
Note original = Note.createNote(Pitch.B, 5, Duration.WHOLE);
// Prints: [Note: [pitch: C#],[octave: 5]]
System.out.println(original.getNoteByScale(Scale.D_MAJOR, 1));
有没有办法修复上面的算法,以便它支持上述情况?
我在Note
和Pitch
以及Scale
类使用的Java类可以在这里找到:Jamie Craane Melody Generation - GoogleCode
答案 0 :(得分:5)
您目前正在测试pitch
(即scale
中的索引)以查看是否需要更改八度音阶。
但是单个音阶可以包含多个八度音程索引,这取决于音符本身相对于C的索引。我没有在这里使用该库的术语,因为我对它不太熟悉。因此,您需要知道相对于C的音符索引是否已经包裹(向下或向上)以便更改八度音程(向上或向下)。
首先假设您的transposeFactor
值不超过6
。接下来在编写任何代码之前定义一些测试(见下文)。然后我认为您只需要进行一些小改动即可使用Pitch.getNoteNumber()
返回相对于C的音符索引。我还假设您的getNoteByScale
方法是Note
上的方法,getScaleIndex
1}}是您在Scale
上创建的方法,如下所示:
// this is a method on Scale
public int getScaleIndex(Pitch pitch) {
return notesInScale.indexOf(pitch);
}
...
public Note getNoteByScale(Scale scale, int transposeFactor) {
// rename to "newPitchIndex" to distinguish from the actual new Pitch object
int newPitchIndex = scale.getScaleIndex(this.pitch) + transposeFactor;
// only use pitch indexes for a scale in the range 0-6
if (newPitchIndex > 6) newPitchIndex -=7;
else if (newPitchIndex < 0) newPitchIndex += 7;
// create the new Pitch object
Pitch newPitch = scale.getNotesInScale().get(newPitchIndex);
// Get the note numbers (relative to C)
int noteNumber = this.pitch.getNoteNumber();
int newNoteNumber = newPitch.getNoteNumber();
int newOctave = this.octave;
if (transposeFactor > 0 && newNoteNumber < noteNumber) newOctave++;
else if (transposeFactor < 0 && newNoteNumber > noteNumber) newOctave--;
return Note.createNote(newPitch, newOctave, this.duration);
}
现在我们有了这个,我们可以轻松地将它扩展到一次超过八度。没有测试,这一切都不会那么容易:
public Note getNoteByScale(Scale scale, int transposeFactor) {
// move not by more than 7
int pitchIndex = scale.getScaleIndex(this.pitch);
int newPitchIndex = (pitchIndex + transposeFactor) % 7;
// and adjust negative values down from the maximum index
if (newPitchIndex < 0) newPitchIndex += 7;
// create the new Pitch object
Pitch newPitch = scale.getNotesInScale().get(newPitchIndex);
// Get the note numbers (relative to C)
int noteNumber = this.pitch.getNoteNumber();
int newNoteNumber = newPitch.getNoteNumber();
// Get the number of whole octave changes
int octaveChanges = transposeFactor / 7;
int newOctave = this.octave + octaveChanges;
// Adjust the octave based on a larger/smaller note index relative to C
if (transposeFactor > 0 && newNoteNumber < noteNumber) newOctave++;
else if (transposeFactor < 0 && newNoteNumber > noteNumber) newOctave--;
return Note.createNote(newPitch, newOctave, this.duration);
}
需要比这更多的测试,但这是一个很好的入门者,所有这些都通过了:
@Test
public void transposeUpGivesCorrectPitch() {
// ┌~1▼
// |D E F♯ G A B ║C♯|D E F♯ G A B ║C♯ |
Note original = Note.createNote(Pitch.B, 5, Duration.WHOLE);
Note actual = original.getNoteByScale(Scale.D_MAJOR, 1);
Note expected = Note.createNote(Pitch.C_SHARP, 6, Duration.WHOLE);
assertEquals(expected.getPitch(), actual.getPitch());
}
@Test
public void transposeDownGivesCorrectPitch() {
// ▼1~┐
// |D E F♯ G A B ║C♯|D E F♯ G A B ║C♯ |
Note original = Note.createNote(Pitch.C_SHARP, 6, Duration.WHOLE);
Note actual = original.getNoteByScale(Scale.D_MAJOR, -1);
Note expected = Note.createNote(Pitch.B, 5, Duration.WHOLE);
assertEquals(expected.getPitch(), actual.getPitch());
}
@Test
public void transposeUpOutsideScaleGivesCorrectPitch() {
// ┌‒1‒~2‒‒3‒‒4▼
// ║C D E F G A B ║C D E F G A B |
Note original = Note.createNote(Pitch.A, 5, Duration.WHOLE);
Note actual = original.getNoteByScale(Scale.C_MAJOR, 4);
Note expected = Note.createNote(Pitch.E, 6, Duration.WHOLE);
assertEquals(expected.getPitch(), actual.getPitch());
}
@Test
public void transposeDownOutsideScaleGivesCorrectPitch() {
// ▼4‒‒3‒‒2‒‒1~┐
// ║C D E F G A B ║C D E F G A B |
Note original = Note.createNote(Pitch.C, 6, Duration.WHOLE);
Note actual = original.getNoteByScale(Scale.C_MAJOR, -4);
Note expected = Note.createNote(Pitch.F, 5, Duration.WHOLE);
assertEquals(expected.getPitch(), actual.getPitch());
}
@Test
public void transposeUpGivesCorrectOctave() {
// ┌~1▼
// |D E F♯ G A B ║C♯|D E F♯ G A B ║C♯ |
Note original = Note.createNote(Pitch.B, 5, Duration.WHOLE);
Note actual = original.getNoteByScale(Scale.D_MAJOR, 1);
Note expected = Note.createNote(Pitch.C_SHARP, 6, Duration.WHOLE);
assertEquals(expected.getOctave(), actual.getOctave());
}
@Test
public void transposeUp2GivesCorrectOctave() {
// ┌~1‒‒2‒‒3‒‒4‒‒5‒‒6‒‒7‒~1▼
// |D E F♯ G A B ║C♯|D E F♯ G A B ║C♯ |
Note original = Note.createNote(Pitch.B, 5, Duration.WHOLE);
Note actual = original.getNoteByScale(Scale.D_MAJOR, 1 + 7);
Note expected = Note.createNote(Pitch.C_SHARP, 7, Duration.WHOLE);
assertEquals(expected.getOctave(), actual.getOctave());
}
@Test
public void transposeDownGivesCorrectOctave() {
// ▼1~┐
// |D E F♯ G A B ║C♯|D E F♯ G A B ║C♯ |
Note original = Note.createNote(Pitch.C_SHARP, 6, Duration.WHOLE);
Note actual = original.getNoteByScale(Scale.D_MAJOR, -1);
Note expected = Note.createNote(Pitch.B, 5, Duration.WHOLE);
assertEquals(expected.getOctave(), actual.getOctave());
}
@Test
public void transposeDown2GivesCorrectOctave() {
// ▼1~‒7‒‒6‒‒5‒‒4‒‒3‒‒2‒‒1~┐
// |D E F♯ G A B ║C♯|D E F♯ G A B ║C♯ |
Note original = Note.createNote(Pitch.C_SHARP, 6, Duration.WHOLE);
Note actual = original.getNoteByScale(Scale.D_MAJOR, -1 - 7);
Note expected = Note.createNote(Pitch.B, 4, Duration.WHOLE);
assertEquals(expected.getOctave(), actual.getOctave());
}
// ... and more tests are needed ...
答案 1 :(得分:2)
我猜你在通过比例结束时不希望+1倍频程(本例中为ScaleIndex 7)。当你通过C时,你想要+1八度音阶。
编辑:
我还没有在java中编程很长时间,我没有为它安装IDE,所以有些我会写一些伪代码(没有类型)。此外,这仅适用于正转置因子,它应该可以轻松调整负数,但我不想再模糊它。 我们的想法是首先要知道我们上升了多少满刻度,这意味着我们必须至少添加那么多八度音阶。 然后添加其余的(必须小于一个满刻度),看看我们在添加那个时是否通过了C.我们通过查看notenumber来做到这一点,如果新的notenumber小于旧的notenumber,我们必须通过C。
public Note getNoteByScale(Scale scale, int transposeFactor) {
numberOfNotesInScale = scale.getNotesInScale().size();
numberOfScalesToTraverseAtLeast = transposeFactor / number_of_notes_in_scale; // this should be integer division
newOctave = this.octave + numberOfScalesToTraverseAtLeast;
restAfterDivide = transposeFactor % numberOfNotesInScale;
newScaleIndex = (scale.getScaleIndex(this.pitch) + restAfterDivide) % numberOfNotesInScale; // we are not gonna do +1 when we pass the end of the scale, so just do a modulo on the number of notes of the scale
newPitch = scale.getNotesInScale().get(newScaleIndex)
if (newPitch.getNoteNumber < this.pitch.getNoteNumber) // then we have passed C, so another octave
newOctave++;
return Note.createNote(scale.getNotesInScale().get(newPitch), newOctave, this.duration);
}
对于某些测试驱动的开发来说,这将是一个理想的候选函数,我至少会围绕它编写一堆单元测试;)
答案 2 :(得分:0)
对比例进行排序,然后使用它。像这样,所有比例音高都在相同的八度音程中。这是我眼中最简单的解决方案。
public Note getNoteByScale(Scale scale, int transposeFactor) {
List<Pitch> tempScale=new ArrayList<Pitch>(scale.getNotesInScale());
Collections.sort(tempScale,new Comparator<Pitch>(){
@Override
public int compare(Pitch a,Pitch b){
if(a.getNoteNumber() == b.getNoteNumber()){
return 0;
}else if(a.getNoteNumber() > b.getNoteNumber()){
return 1;
}else{
return -1;
}
}
});
int index = (tempScale.indexOf(this.pitch) + transposeFactor );
int newPitch = ((index % 7)+7)%7;
int newOctave;
if(index>0){
newOctave = this.octave + index/7;
}else{
newOctave = this.octave + (index-6)/7;
}
return Note.createNote(tempScale.get(newPitch),
newOctave, this.duration);
}
编辑:现在它通过了所有测试
在@AndyBrown的单元测试中添加了两个案例:
@Test
public void transposeDown6GivesCorrectOctave() {
Note original = Note.createNote(Pitch.C_SHARP, 6, Duration.WHOLE);
Note actual = original.getNoteByScale(Scale.D_MAJOR, -1-7-7-7-7-7);
Note expected = Note.createNote(Pitch.B, 0, Duration.WHOLE);
assertEquals(expected.getOctave(), actual.getOctave());
}
@Test
public void transposeUp5GivesCorrectOctave() {
Note original = Note.createNote(Pitch.B, 5, Duration.WHOLE);
Note actual = original.getNoteByScale(Scale.D_MAJOR, 1+7+7+7+7);
Note expected = Note.createNote(Pitch.C_SHARP, 10, Duration.WHOLE);
assertEquals(expected.getOctave(), actual.getOctave());
}
答案 3 :(得分:0)
是的,我知道这是一个老问题,但我需要类似的东西。基本上问题可以解析为以下内容:给出一个音符(“D”)和一些正或负半步(1),返回一个新音符,即原始音符转置(D#)
这里的答案太复杂了。
试试这个:
public class Transposer {
enum TARGET { FLAT, SHARP };
static String[] SHARPS = { "A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#" };
static String[] FLATS = { "A", "Bb", "B", "C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab" };
static String transpose(String note, int halfSteps, TARGET target) {
if (target == null) {
// By default we like flats
target = TARGET.FLAT;
}
String[] scale = FLATS;
if (target == TARGET.SHARP) {
scale = SHARPS;
}
int index = findIndex(scale, note);
if (index < 0) return note; // Not found, just return note
String everythingelse = "";
if (note.length() > scale[index].length()) {
everythingelse = note.substring(scale[index].length());
}
index = index + halfSteps;
while (index < 0) {
index += scale.length; // Make the index positive
}
index = index % scale.length;
return scale[index]+everythingelse;
}
public static int findIndex(String[] scale, String note) {
int r = -1;
String root = note.substring(0,1);
if (note.charAt(1) == '#' || note.charAt(1) == 'b') {
root = note.substring(0,2);
}
for (int i=0; i<scale.length; ++i) {
if (scale[i].equalsIgnoreCase(root)) {
// Match.
return i;
}
}
return r;
}
public static void main(String args[]) {
String note = args[0];
int halfsteps = Integer.parseInt(args[1]);
System.out.println(note+" transposed "+halfsteps+" halfsteps is "+transpose(note, halfsteps, TARGET.FLAT));
}
}
main()是测试:
java -cp . Transposer A 1
A transposed 1 halfsteps is Bb
java -cp . Transposer Eb -3
Eb transposed -3 halfsteps is C
java -cp Transposer C7 2
C7 transposed 2 halfsteps is D7
采用比例是微不足道的:
String[] scale = { "C", "D", "E", "F", "G", "A", "B" };
for (String snote: scale) {
System.out.print(Transposer.transpose(snote,1,null));
}
System.out.println();
=> Db Eb F Gb Ab Bb C