Baroque Music Technology
Upload a MusicXML file containing a bass line to generate an historically-informed continuo realization using rules from Gasparini (1708), Delair (1724), and the Wead–Knopke decision tree (ICMC 2007).
Analyzing bass line and realizing harmony…
Output preview
The realizer implements the decision tree described by Wead & Knopke (ICMC 2007), drawing on two primary Baroque treatises:
| Triads | ||
| (none) / 5 | 5 | root position triad |
| 6 | 6 | first inversion triad |
| 6 4 | 6 4 | second inversion triad — cadential or passing |
| Seventh Chords | ||
| 7 | 7 5 | seventh chord, root position |
| 6 5 | 6 5 | seventh chord, first inversion |
| 4 3 | 4 3 | seventh chord, second inversion / 4–3 suspension |
| 2 (= 4 2) | 4 2 | seventh chord, third inversion |
| Ninths | ||
| 9 | 9 5 | ninth chord / 9–8 suspension |
| 9 7 | 9 7 5 | ninth with seventh |
| Suspensions | ||
| 7 6 | 7 6 | 7–6 suspension |
| 5 4 | 5 4 | 5–4 (–3) suspension |
| Special | ||
| 8 | 8 | explicit octave above bass |
| ♯ / ♭ / ♮ | accidental prefix — ♯ raises, ♭ lowers, ♮ cancels the figured interval | |
Il Soprano deve stare fra il Do e il Sol del quinto rigo; il Contralto fra il Sol del terzo e il Do del quinto; il Tenore fra il Do del terzo e il Mi del quarto.
The Soprano must stay between C4 and G5; the Alto between G3 and C5; the Tenor between C3 and E4.
$cost = 0.0;
$ranges = $ctx['ranges'];
$voiceNames = ['tenor', 'alto', 'soprano'];
foreach ($ctx['curr'] as $i => $midi) {
$vName = $voiceNames[$i] ?? 'soprano';
[$lo, $hi] = $ranges[$vName];
if ($midi < $lo) { $cost += ($lo - $midi) * 3; }
if ($midi > $hi) { $cost += ($midi - $hi) * 3; }
}
return $cost;
Il Tenore non deve scendere sotto il Sol del terzo rigo (Sol3) per evitare confusione con il Basso.
The Tenor must never descend below G3 (MIDI 55) to avoid muddiness in the bass register.
$tenor = $ctx['curr'][0] ?? 55;
if ($tenor < 55) { return (55 - $tenor) * 50.0; }
return 0.0;
Il terzo dell'accordo deve essere sempre presente nelle voci superiori per garantire la piena sonorità dell'accordo.
The note a diatonic third above the bass must always be present in the upper voices. Omitting the third produces a hollow, incomplete sound.
$tonicPc = $ctx['keyMode'] === 'minor'
? ((($ctx['keyFifths'] * 7) - 3) % 12 + 12) % 12
: (($ctx['keyFifths'] * 7) % 12 + 12) % 12;
$steps = $ctx['keyMode'] === 'minor' ? [0,2,3,5,7,8,10] : [0,2,4,5,7,9,11];
$scalePcs = array_map(fn($i) => ($tonicPc + $i) % 12, $steps);
$bassPc = $ctx['bassCurr'] % 12;
$deg = array_search($bassPc, $scalePcs);
if ($deg === false) { return 0.0; } // chromatic bass — skip
$thirdPc = $scalePcs[($deg + 2) % 7];
$upperPcs = array_map(fn($m) => $m % 12, $ctx['curr']);
if (!in_array($thirdPc, $upperPcs, true)) { return 25.0; }
return 0.0;
Le dessus ne doit jamais aller au-delà du mi ou du fa du cinquième rang.
The soprano must never go beyond E5 (or at most F5). Penalise any soprano pitch above MIDI 76 (E5).
$soprano = $ctx['curr'][2] ?? 76;
if ($soprano > 76) { return ($soprano - 76) * 5.0; }
return 0.0;
La distanza fra il Tenore e il Soprano nella mano destra non deve superare la nona.
The span between Tenor and Soprano in the right hand must not exceed a ninth (14 semitones).
if (count($ctx['curr']) < 3) { return 0.0; }
$span = $ctx['curr'][2] - $ctx['curr'][0]; // soprano - tenor
if ($span > 14) { return ($span - 14) * 10.0; }
return 0.0;
Il faut éviter les quintes consécutives entre toutes les parties.
Consecutive (parallel) perfect fifths between any pair of voices must be avoided.
if ($ctx['isStart'] || count($ctx['prev']) < 3) { return 0.0; }
$cost = 0.0;
$allCurr = array_merge($ctx['curr'], [$ctx['bassCurr']]);
$allPrev = array_merge($ctx['prev'], [$ctx['bassPrev']]);
$n = count($allCurr);
for ($a = 0; $a < $n; $a++) {
for ($b = $a + 1; $b < $n; $b++) {
if (!isset($allPrev[$a]) || !isset($allPrev[$b])) { continue; }
$prevInt = abs($allPrev[$a] - $allPrev[$b]) % 12;
$currInt = abs($allCurr[$a] - $allCurr[$b]) % 12;
$moved = ($allPrev[$a] !== $allCurr[$a]) || ($allPrev[$b] !== $allCurr[$b]);
if ($moved && $prevInt === 7 && $currInt === 7) { $cost += 40.0; }
}
}
return $cost;
La nota sensibile non si raddoppia mai, essendo nota a risoluzione obbligata verso la tonica.
The leading tone (semitone below the tonic) must never be doubled in any pair of voices, since it carries an obligatory upward resolution.
$tonicPc = $ctx['keyMode'] === 'minor'
? ((($ctx['keyFifths'] * 7) - 3) % 12 + 12) % 12
: (($ctx['keyFifths'] * 7) % 12 + 12) % 12;
$ltPc = ($tonicPc + 11) % 12;
$allPcs = array_map(fn($m) => $m % 12, array_merge($ctx['curr'], [$ctx['bassCurr']]));
$count = count(array_filter($allPcs, fn($pc) => $pc === $ltPc));
if ($count > 1) { return 60.0 * ($count - 1); }
return 0.0;
Man verdoppele niemals eine chromatisch erhöhte Note, die als Leitton fungiert.
Never double a chromatically altered note that functions as a leading tone (i.e. any note outside the diatonic scale of the current key). One occurrence is permissible; two or more are forbidden.
$tonicPc = $ctx['keyMode'] === 'minor'
? ((($ctx['keyFifths'] * 7) - 3) % 12 + 12) % 12
: (($ctx['keyFifths'] * 7) % 12 + 12) % 12;
$steps = $ctx['keyMode'] === 'minor' ? [0,2,3,5,7,8,10] : [0,2,4,5,7,9,11];
$scalePcs = array_map(fn($i) => ($tonicPc + $i) % 12, $steps);
$allPcs = array_map(fn($m) => $m % 12, array_merge($ctx['curr'], [$ctx['bassCurr']]));
$cost = 0.0;
$chromCounts = [];
foreach ($allPcs as $pc) {
if (!in_array($pc, $scalePcs, true)) {
$chromCounts[$pc] = ($chromCounts[$pc] ?? 0) + 1;
}
}
foreach ($chromCounts as $count) {
if ($count > 1) { $cost += 50.0 * ($count - 1); }
}
return $cost;
La dissonance de septième ne se double jamais dans aucune des parties.
The dissonant seventh of a chord (the pitch a minor or major seventh above the bass) must never be doubled in any pair of voices.
$bassPc = $ctx['bassCurr'] % 12;
$seventhCount = 0;
foreach ($ctx['curr'] as $m) {
$interval = ($m % 12 - $bassPc + 12) % 12;
if ($interval === 10 || $interval === 11) { $seventhCount++; }
}
if ($seventhCount > 1) { return 50.0 * ($seventhCount - 1); }
return 0.0;
La dissonance de neuvième ne se double jamais dans aucune des parties.
The dissonant ninth must never be doubled in any pair of voices. Only one voice may hold a ninth above the bass at a time.
$bassPc = $ctx['bassCurr'];
$count = 0;
foreach ($ctx['curr'] as $m) {
$sem = $m - $bassPc;
// Ninth = 13 (minor) or 14 (major) semitones above bass; also allow compound ninths
if ($sem === 13 || $sem === 14 || $sem === 25 || $sem === 26) { $count++; }
}
if ($count > 1) { return 50.0 * ($count - 1); }
return 0.0;
Il faut éviter les octaves consécutives entre toutes les parties.
Consecutive (parallel) octaves or unisons between any pair of voices must be avoided.
if ($ctx['isStart'] || count($ctx['prev']) < 3) { return 0.0; }
$cost = 0.0;
$allCurr = array_merge($ctx['curr'], [$ctx['bassCurr']]);
$allPrev = array_merge($ctx['prev'], [$ctx['bassPrev']]);
$n = count($allCurr);
for ($a = 0; $a < $n; $a++) {
for ($b = $a + 1; $b < $n; $b++) {
if (!isset($allPrev[$a]) || !isset($allPrev[$b])) { continue; }
$prevInt = abs($allPrev[$a] - $allPrev[$b]) % 12;
$currInt = abs($allCurr[$a] - $allCurr[$b]) % 12;
$moved = ($allPrev[$a] !== $allCurr[$a]) || ($allPrev[$b] !== $allCurr[$b]);
if ($moved && $prevInt === 0 && $currInt === 0) { $cost += 60.0; }
}
}
return $cost;
La nota sensibile deve sempre salire verso la tonica; è vietato scendere o fermarsi su di essa.
The leading tone must always resolve upward; moving down from or remaining on the leading tone is forbidden.
if ($ctx['isStart'] || count($ctx['prev']) < 3) { return 0.0; }
$tonicPc = $ctx['keyMode'] === 'minor'
? ((($ctx['keyFifths'] * 7) - 3) % 12 + 12) % 12
: (($ctx['keyFifths'] * 7) % 12 + 12) % 12;
$ltPc = ($tonicPc + 11) % 12;
$cost = 0.0;
foreach ($ctx['curr'] as $i => $curr) {
$prev = $ctx['prev'][$i] ?? null;
if ($prev === null || ($prev % 12) !== $ltPc) { continue; }
if ($curr <= $prev) { $cost += 40.0; } // stayed or went down — must resolve up
}
return $cost;
La septième dissonante doit toujours se résoudre en descendant d'un degré.
Whatever the case, the dissonant seventh invariably resolves one step downward. A voice that held a seventh above the previous bass must move down in the current chord.
if ($ctx['isStart'] || count($ctx['prev']) < 3) { return 0.0; }
$prevBassPc = $ctx['bassPrev'] % 12;
$cost = 0.0;
foreach ($ctx['curr'] as $i => $curr) {
$prev = $ctx['prev'][$i] ?? null;
if ($prev === null) { continue; }
$interval = ($prev % 12 - $prevBassPc + 12) % 12;
if ($interval === 10 || $interval === 11) {
if ($curr >= $prev) { $cost += 50.0; } // must resolve downward
}
}
return $cost;
La quarte suspendue se résout toujours en descendant d'un degré vers la tierce (4–3).
A suspended fourth occurring in any upper voice is always sustained and resolved downward by step to the third. A voice that held a fourth (5 semitones) above the previous bass must move down.
if ($ctx['isStart'] || count($ctx['prev']) < 3) { return 0.0; }
$prevBassPc = $ctx['bassPrev'] % 12;
$cost = 0.0;
foreach ($ctx['curr'] as $i => $curr) {
$prev = $ctx['prev'][$i] ?? null;
if ($prev === null) { continue; }
$interval = ($prev % 12 - $prevBassPc + 12) % 12;
if ($interval === 5) {
if ($curr >= $prev) { $cost += 45.0; } // must resolve downward
}
}
return $cost;
La neuvième doit toujours se résoudre en descendant vers l'octave.
The suspended ninth invariably resolves downward by step to the octave. Any upper voice holding a ninth (13 or 14 semitones) above the preceding bass must move down in the next chord.
if ($ctx['isStart'] || count($ctx['prev']) < 3) { return 0.0; }
$cost = 0.0;
foreach ($ctx['curr'] as $i => $curr) {
$prev = $ctx['prev'][$i] ?? null;
if ($prev === null) { continue; }
$sem = $prev - $ctx['bassPrev'];
if ($sem === 13 || $sem === 14 || $sem === 25 || $sem === 26) {
if ($curr >= $prev) { $cost += 45.0; }
}
}
return $cost;
La quinta eccedente (#5), quando si trova nella voce superiore, deve sempre salire alla sesta.
The augmented fifth (#5 = 8 semitones above the bass), when it appears in the soprano (top voice), must resolve upward to the sixth. It acts as a secondary leading tone.
if ($ctx['isStart'] || count($ctx['prev']) < 3) { return 0.0; }
$soprano = $ctx['curr'][2];
$sopranoPrev = $ctx['prev'][2];
$interval = ($sopranoPrev - $ctx['bassPrev'] + 1200) % 12;
if ($interval === 8) { // augmented fifth mod 12
if ($soprano <= $sopranoPrev) { return 30.0; } // must resolve upward
}
return 0.0;
Les quintes et octaves cachées entre la basse et le soprano sont défendues quand le soprano monte par saut.
Hidden (direct) fifths and octaves between outer voices are forbidden when the soprano moves by leap.
if ($ctx['isStart'] || count($ctx['prev']) < 3) { return 0.0; }
$soprano = $ctx['curr'][2];
$sopranoPrev = $ctx['prev'][2];
$sopranoLeap = abs($soprano - $sopranoPrev) > 2;
if (!$sopranoLeap) { return 0.0; }
$currOuterInt = abs($soprano - $ctx['bassCurr']) % 12;
$bothSameDir = (($soprano - $sopranoPrev) > 0) === (($ctx['bassCurr'] - $ctx['bassPrev']) > 0);
if ($bothSameDir && ($currOuterInt === 7 || $currOuterInt === 0)) { return 30.0; }
return 0.0;
Le parti non si devono incrociare l'una con l'altra.
The voices must not cross one another.
$cost = 0.0;
for ($i = 0; $i < count($ctx['curr']) - 1; $i++) {
if ($ctx['curr'][$i] > $ctx['curr'][$i + 1]) { $cost += 100.0; }
}
return $cost;
Si la même note se trouve dans l'accord précédent, on la retient dans la même partie. Exception : si c'est le même accord répété, on change de position pour éviter de monter trop haut ou de descendre trop bas.
If a pitch class is common to both chords, keep it in the same voice at the same octave. Exception: if the harmony is literally repeated, shift voices toward their range centres to avoid drifting to extremes.
if ($ctx['isStart'] || count($ctx['prev']) < 3) { return 0.0; }
$cost = 0.0;
$prevPcs = array_values(array_unique(array_map(fn($m) => $m % 12, array_merge($ctx['prev'], [$ctx['bassPrev']]))));
$currPcs = array_values(array_unique(array_map(fn($m) => $m % 12, array_merge($ctx['curr'], [$ctx['bassCurr']]))));
$aPrev = $prevPcs; $aCurr = $currPcs; sort($aPrev); sort($aCurr);
$sameChord = ($aPrev === $aCurr);
if ($sameChord) {
$names = ['tenor', 'alto', 'soprano'];
foreach ($ctx['curr'] as $i => $midi) {
$prev = $ctx['prev'][$i] ?? null;
if ($prev === null) { continue; }
[$lo, $hi] = $ctx['ranges'][$names[$i]];
if ($midi === $prev && abs($midi - ($lo + $hi) / 2) > ($hi - $lo) * 0.35) {
$cost += 3.0;
}
}
return $cost;
}
foreach ($ctx['curr'] as $i => $midi) {
$prev = $ctx['prev'][$i] ?? null;
if ($prev === null) { continue; }
if (in_array($prev % 12, $currPcs, true) && ($midi % 12) !== ($prev % 12)) {
$cost += 8.0;
}
}
return $cost;
La quarte suspendue ne se double jamais: une seule voix peut tenir la quarte au-dessus de la basse.
The suspended fourth must never be doubled. Only one upper voice may hold a fourth (5 semitones) above the bass at a time, since it is a dissonance requiring a single prepared resolution.
$bassPc = $ctx['bassCurr'];
$count = 0;
foreach ($ctx['curr'] as $m) {
$interval = ($m - $bassPc + 1200) % 12;
if ($interval === 5) { $count++; } // perfect fourth = 5 semitones
}
if ($count > 1) { return 50.0 * ($count - 1); }
return 0.0;
Prefer common tones, then stepwise motion; penalize leaps according to size.
Common tones cost 0; steps (≤2) cost 1; small leaps (3–4) cost 4; leaps of a 5th/6th (5–7) cost 9; large leaps cost 3× the interval size.
if ($ctx['isStart'] || empty($ctx['prev'])) { return 0.0; }
$cost = 0.0;
foreach ($ctx['curr'] as $i => $midi) {
$prev = $ctx['prev'][$i] ?? null;
if ($prev === null) { continue; }
$motion = abs($midi - $prev);
if ($motion === 0) { /* common tone */ }
elseif ($motion <= 2) { $cost += 1.0; }
elseif ($motion <= 4) { $cost += 4.0; }
elseif ($motion <= 7) { $cost += 9.0; }
else { $cost += $motion * 3.0; }
}
return $cost;
Avec la septième, il vaut mieux jouer la tierce et la quinte que la tierce et l'octave.
When realizing a seventh chord, it is better to include the fifth rather than the octave of the bass. If the chord contains a seventh but has an octave doubling of the bass instead of a fifth, apply a soft penalty.
$bassPc = $ctx['bassCurr'] % 12;
$allPcs = array_map(fn($m) => $m % 12, $ctx['curr']);
$hasSeventh = false;
foreach ($allPcs as $pc) {
$iv = ($pc - $bassPc + 12) % 12;
if ($iv === 10 || $iv === 11) { $hasSeventh = true; break; }
}
if (!$hasSeventh) { return 0.0; }
$hasFifth = false;
$hasOctave = false;
foreach ($allPcs as $pc) {
$iv = ($pc - $bassPc + 12) % 12;
if ($iv === 7) { $hasFifth = true; }
if ($iv === 0) { $hasOctave = true; }
}
if ($hasOctave && !$hasFifth) { return 15.0; }
return 0.0;
Le Soprano et la Basse doivent, autant que possible, se mouvoir en sens contraire.
The Soprano and Bass should move in contrary motion whenever possible. Similar motion between outer voices is penalised.
if ($ctx['isStart'] || count($ctx['prev']) < 3) { return 0.0; }
$bassDir = $ctx['bassCurr'] - $ctx['bassPrev'];
$sopDir = $ctx['curr'][2] - $ctx['prev'][2];
if ($bassDir === 0 || $sopDir === 0) { return 0.0; }
if (($bassDir > 0) === ($sopDir > 0)) { return 6.0; }
return 0.0;
Dans une suite de septièmes consécutives avec la basse montant par quarte ou descendant par quinte, on alterne la quinte et l'octave: quinte au premier accord, octave au second, et ainsi de suite.
In a sequence of seventh chords where the bass moves up a fourth or down a fifth, alternate between playing the fifth (and omitting the octave) in one chord and the octave (omitting the fifth) in the next. Two consecutive seventh chords that both include the fifth cause parallel fifths; two that both omit it sound thin.
if ($ctx['isStart'] || count($ctx['prev']) < 3) { return 0.0; }
// Only applies when a 7th was present in the previous chord
$prevBassPc = $ctx['bassPrev'] % 12;
$hasPrevSeventh = false;
foreach ($ctx['prev'] as $m) {
$iv = ($m % 12 - $prevBassPc + 12) % 12;
if ($iv === 10 || $iv === 11) { $hasPrevSeventh = true; break; }
}
if (!$hasPrevSeventh) { return 0.0; }
// Check if current chord also has a seventh
$currBassPc = $ctx['bassCurr'] % 12;
$hasCurrSeventh = false;
foreach ($ctx['curr'] as $m) {
$iv = ($m % 12 - $currBassPc + 12) % 12;
if ($iv === 10 || $iv === 11) { $hasCurrSeventh = true; break; }
}
if (!$hasCurrSeventh) { return 0.0; }
// Bass motion: up a 4th (5 semitones) or down a 5th (7 semitones)
$bassMotion = ($ctx['bassCurr'] - $ctx['bassPrev'] + 12) % 12;
if ($bassMotion !== 5 && $bassMotion !== 7) { return 0.0; }
// Check if both chords have a fifth — that causes parallel fifths → penalise
$prevHasFifth = false; $currHasFifth = false;
foreach ($ctx['prev'] as $m) { if (($m % 12 - $prevBassPc + 12) % 12 === 7) { $prevHasFifth = true; break; } }
foreach ($ctx['curr'] as $m) { if (($m % 12 - $currBassPc + 12) % 12 === 7) { $currHasFifth = true; break; } }
if ($prevHasFifth && $currHasFifth) { return 20.0; } // both have fifth → likely parallel fifths
return 0.0;