我正在尝试创建一种算法,该算法根据音符的给定音阶和音调因子向上或向下进行音调转换。
当前算法:
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大音阶中5倍 Octave 的'B'音符,该算法将按预期工作并返回音符' C'(音阶的索引0)在8个 Octave 音阶上。
但是,假设我们使用了由音符组成的D级音阶
D, E, F♯, G, A, B, and C♯
对于此音阶,应该认为最后一个音符“C♯”比音阶中其余音符高 Octave 。例如,如果我们使用上面的算法将音符“B”(索引6)以5个 Octave 音阶换位,那么该算法实际上将音符“C♯”以5个 Octave 音阶换位,这是完全错误的:应为6的 Octave 。
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 最佳答案
您目前正在测试pitch
(即scale
中的索引)以查看是否需要更改 Octave 。
但是一个音阶可以包含一个以上的 Octave 音阶索引,这取决于音符本身相对于C的索引。我在这里不使用该库的术语,因为我不太熟悉它。因此,您需要知道相对于C的音符索引是否已包装(向下或向上)以更改 Octave (向上或向下)。
首先,假设您的移动不超过transposeFactor
的6
值。接下来,在编写任何代码之前定义一些测试(请参见下文)。然后我认为您只需要稍作更改即可使用Pitch.getNoteNumber()
返回相对于C的音符索引。我还假设您的getNoteByScale
方法是Note
的方法,而getScaleIndex
是您在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);
}
现在我们有了它,我们可以轻松地扩展它一次将其移动超过 Octave 。没有测试,这一切真的没有那么容易:
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 ...