我正在尝试创建一种算法,该算法根据音符的给定音阶和音调因子向上或向下进行音调转换。

当前算法:

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));

有什么办法可以修复上述算法,从而支持上述情况?

可以在此处找到用于NotePitchScale类的Java类:Jamie Craane Melody Generation - GoogleCode

最佳答案

您目前正在测试pitch(即scale中的索引)以查看是否需要更改 Octave 。

但是一个音阶可以包含一个以上的 Octave 音阶索引,这取决于音符本身相对于C的索引。我在这里不使用该库的术语,因为我不太熟悉它。因此,您需要知道相对于C的音符索引是否已包装(向下或向上)以更改 Octave (向上或向下)。

首先,假设您的移动不超过transposeFactor6值。接下来,在编写任何代码之前定义一些测试(请参见下文)。然后我认为您只需要稍作更改即可使用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 ...

07-26 02:52