More thoughts on Figured Bass realisation

classic Classic list List threaded Threaded
3 messages Options
Reply | Threaded
Open this post in threaded view
|

More thoughts on Figured Bass realisation

Maurizio M. Gavioli

During the discussion on the recent PR for Figured Bass realisation, by going by bits and pieces, things became unncessarily complex and the global picture harder to get.

My impression is that, once we start from the basic 'rule': "sharp => major | flat => minor", there is a quite simpler way to proceed, as I think that there is only one piece of info needed from the context of the F.b. chord, i.e. the current key signature; everything else can be rather easily computed from the F.b. data themselves with a handful of code lines and a few table lookups.

Each interval can be diminished, minor, neutral-or-just, major, augmented; with 7 intervals from unison to 7th, there are 35 possibilities for the pitch offset and the TPC offset of each interval type:

Pitch offset

        dimin.   minor    n.o.j    major    augm.
Unison    -1      --        0        --       +1
Second     0      +1        ?        +2       +3
Third     +2      +3        ?        +4       +5
Fourth    +4      --        5        --       +6

and so on. Same for TPC offsets:

Unison    -7      --        0        --       +7
Second   -12      -5        ?        +2       +9
Third    -10      -3        ?        +4      +13
Fourth    -8      --       -1        --       +6

and so on. Some combinations do not exist (the '--': unison, 4th anf 5th cannot be minor or major) and can be filled with arbitrary values as they will never be used. Some cells (the '?': neutral 2nd, 3rd, 6th and 7th) cannot be computed without knowing the current key signature: more about them later.

From the F.b. figures, the interval number is easy; for the interval type, let's start from the basics: for 2b/Ø/#, 3b/Ø/#, 6b/Ø/, 7b/Ø/#:

b => minor
Ø => neutral
# => major

For 4b/Ø/# and 5b/Ø/#:

b => diminished
Ø => just
# => augmented

Once we know the interval to compute, for all combinations except the '?', the derived note can be computed quite easily:

Note* derivedNote = new Note(score);

int	derivedNotePitchOffset	= pitchOffsetTable[intervalNumber][intervalType];
int	derivedNoteTPCOffset	= tpcOffsetTable[intervalNumber][intervalType];

derivedNote->setPitch(bassNote->pitch() + derivedNotePitchOffset,
		bassNote->tpc1() + derivedNoteTPCOffset,
		bassNote->tpc2() + derivedNoteTPCOffset);

If the TPC lookup returned '?' (the Tpc::TPC_INVALID enum value can be used for it), we have to position the bass note in the current key signature scale and retrieve the derived note as specified by the same scale. Note that any degree, 'native' or altered, of a scale has a known relationship in pitch and in TPC with the tonic:

            Pitch    TPC   Degree
Degree 1 b    -1     -7      0
Degree 1       0      0      0  *
Degree 1 #    +1     +7      0
Degree 2 b    +1     -5      1
Degree 2      +2     +2      1  *
Degree 2 #    +3     +9      1
Degree 3 b    +3     -3      2
Degree 3      +4     +4      2  *
Degree 3 #    +5    +13      2

and so on (ignore the '*' for the moment). This table is shown above sorted by degree order, but it is actually accessed by TPC, so it is better to sort it by increasing TPC value.

Another table is handy to have, which is derived from the above table by keeping only the lines marked with an * (the 'native' scale degrees), sorted by degree.

The current key sig. for the bass staff can be obtained and converted into the TPC of its tonic and the bass note TPC can be converted into a scale degree with:

Chord bassChord		= bassNote->chord();
Key key			= bassChord->staff()->key(bassChord->tick());
int tonicTPC		= key + (Tpc::TPC_C - Key::C);
int bassNoteTPCOffset	= bassNote->tpc1() - tonicTPC;
int bassNotePitchOffset	= scaleTable[bassNoteTPCOffset][PITCH];
int bassNoteDegree	= scaleTable[bassNoteTPCOffset][DEGREE];
// locate the derived scale degree
int derivedNoteDegree	= bassNoteDegree + intervalnumber;
// locate the derived note pitch and TPC position relative to the scale tonic
int derivedNotePitchOffset	= degreeTable[derivedNoteDegree][PITCH];
int derivedNoteTPCOffset	= degreeTable[derivedNoteDegree][TPC];
// ready to fill the derived note values
derivedNote->setPitch(
		bassNote->pitch() - bassNotePitchOffset + derivedNotePitchOffset,
		bassNote->tpc1()  - bassNoteTPCOffset   + derivedNoteTPCOffset,
		bassNote->tpc2()  - bassNoteTPCOffset   + derivedNoteTPCOffset);

Finally, the derived note can be added to its destination chord with:

derivedNote->setParent(derivedNoteChord);
score->undoAddElement(derivedNote);

An attempt of a function returning a derived note from a bass note, an interval number and an interval type could be:

Note * createDerivedNote(Note* bassNote, int intervalnumber, int intervaltype)
{
	Note* derivedNote = new Note(score);

	int	derivedNotePitchOffset	= pitchOffsetTable[intervalNumber][intervalType];
	int	derivedNoteTPCOffset	= tpcOffsetTable[intervalNumber][intervalType];

	if (derivedNoteTPCOffset != Tpc::TPC_INVALID) {
		derivedNote->setPitch(bassNote->pitch() + derivedNotePitchOffset,
				bassNote->tpc1() + derivedNoteTPCOffset,
				bassNote->tpc2() + derivedNoteTPCOffset);
		}
	else {
		Key key			= bassChord->staff()->key(bassChord->tick());
		int tonicTPC		= key + (Tpc::TPC_C - Key::C);
		int bassNoteTPCOffset	= bassNote->tpc1() - tonicTPC;
		int bassNotePitchOffset	= scaleTable[bassNoteTPCOffset][PITCH];
		int bassNoteDegree	= scaleTable[bassNoteTPCOffset][DEGREE];
		// locate the derived scale degree
		int derivedNoteDegree	= bassNoteDegree + intervalnumber;
		// locate the derived note pitch and TPC position relative to the scale tonic
		int derivedNotePitchOffset	= degreeTable[derivedNoteDegree][PITCH];
		int derivedNoteTPCOffset	= degreeTable[derivedNoteDegree][TPC];
		// ready to fill the derived note values
		derivedNote->setPitch(
				bassNote->pitch() - bassNotePitchOffset + derivedNotePitchOffset,
				bassNote->tpc1()  - bassNoteTPCOffset   + derivedNoteTPCOffset,
				bassNote->tpc2()  - bassNoteTPCOffset   + derivedNoteTPCOffset);
		}
		return derivedNote;
}

Probably, it is wise to add a few tests on parameters and TPC over/underflow and to factorize out the key sig. function call, which is known not to change at least within each measure and is probably the most expensive function call.

It is quite possible that some of the above tables already exist in the current code base, as rather similar tasks are needed in, for instance, diatonic transposition.

Hoping it helps,

Maurizio

Reply | Threaded
Open this post in threaded view
|

Re: More thoughts on Figured Bass realisation

Jim Newton
Hi Maurizo,   I have a question about the undo step.  Once the menu item to add the derived notes to the staff has been called, I would like UNDO to undo all of them in one fell swoop, not one note at a time.  Does your use of score->undoAddElement(derivedNote) force one not to be undone at a time?
Reply | Threaded
Open this post in threaded view
|

Re: More thoughts on Figured Bass realisation

berteh
In reply to this post by Maurizio M. Gavioli
Dear Maurizio,

I've been pointed recently to this interesting discussion, thank you for the clear explanations. Wish I would have spotted it earlier, as I wanted to generate notes from Chords in MuseScore.

I ended up following a similar line of thought, converting a pitch offset (in semitones) to a tpc offset.

Nevertheless, as I am interested in handling most common chords notations, and not any intervals, I thought I could simplify the interpretation by going in general for the just/augmented interval but for a few that are usually minor/diminished (3, 6, 10).

If I take a table notation as in the post you refer to that would show like:

pitch offset      tpc offset
(from root)      (from root)
0                    0
1                    7
2                    2
3                   -3   (minor third)
4                    4
5                   -1  
6                   -6  (diminished fifth)
7                    1
8                    8
9                    3
10                  -2  (diminished ninth)
11                   5


In javascript/QML that looks like: (https://github.com/berteh/musescore-chordsToNotes/blob/master/chordsToNotes.qml#L133)

/** returns tpc of note that is #semitone half-tones higher than rootTPC
    eg: semitoneToTPC (tpc of C#, 4) = tpc of E# */

function semitoneToTPC(rootTpc, semitone){
  var semiToTpcDiff = [0, 7, 2, -3, 4, -1, -6, 1, 8, 3, -2, 5];
  return (rootTpc + semiToTpcDiff[semitone%12]);
}

Result can be seen/heard in the musescore file "Chord Chard after Generation" (https://github.com/berteh/musescore-chordsToNotes/blob/master/test/Chord_Chart_afterGeneration.mscz).

Kindly let me know what you think of this simplification!
Berteh.