mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-08-12 11:49:28 +00:00
feat(highway): render chord harmony fn.rn + voicing on 2D + 3D (§6.3.1, §6.6) (#541)
* feat(core): carry chord harmony fn + template voicing on the wire (§6.3.1, §6.6)
Add two OPTIONAL per-chord harmony annotations (feedpak 1.7.0), mirroring the
teaching-marks (fg/ch/sd) wire work:
- Chord.fn (instance): {rn, q, deg} harmonic-function object, key-dependent.
Validated by _validate_fn on BOTH decode and emit so a partial / out-of-range
fn (which would fail the schema's required-keys rule) never rides the wire.
Default-omitted, mirroring bend bnv.
- ChordTemplate.voicing (template): key-independent voicing-type string
("open", "triad", "shell", "drop2", "barre", ...). Emitted only when
non-empty; non-string wire values fall back to "".
Display/teaching only — never fed to a grader (honesty rule). fn auto-derivation
is DEFERRED (carry-only): a complete rn/q needs chord-quality analysis, and a
deg-only fn would be schema-invalid, so server.py carries author-provided fn
unchanged. GP import unchanged (no reliable per-chord function/voicing).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(highway): render chord harmony fn.rn + voicing on 2D + 3D (§6.3.1, §6.6)
Draw the chord's harmonic-function Roman numeral (instance fn.rn) and its
template voicing string, stacked above the chord name on both highways. A shared
pure helper chordHarmonyLabels(fn, voicing) formats the two labels (empty when
absent/malformed) and is node-tested against both files.
Both labels are gated behind the EXISTING teaching-marks opt-in
(_showTeachingMarks / teachingMarksVisible bundle flag) — they're chord-level
teaching overlays, same class as sd/ch, so they stay off the default highway.
2D guards the empty-note-chord case; 3D reuses the gold chord-label sprite style.
Render only — no scoring / NoteVerifier path is touched (honesty rule).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
ea22791984
commit
3fc077cbc1
@@ -0,0 +1,49 @@
|
||||
// Behavioural tests for the chord harmony-annotation render helper
|
||||
// chordHarmonyLabels (§6.3.1 / §6.6), shared by the 2D and 3D highways.
|
||||
// Pure, so we extract the function source by brace-matching and eval it in
|
||||
// isolation — same pattern as highway_teaching_marks.test.js.
|
||||
|
||||
const { test } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
function extractFn(src, name) {
|
||||
const start = src.indexOf('function ' + name);
|
||||
assert.ok(start >= 0, `function ${name} must exist`);
|
||||
const open = src.indexOf('{', start);
|
||||
let depth = 0;
|
||||
for (let i = open; i < src.length; i++) {
|
||||
if (src[i] === '{') depth++;
|
||||
else if (src[i] === '}' && --depth === 0) return src.slice(start, i + 1);
|
||||
}
|
||||
throw new Error(`unbalanced braces extracting ${name}`);
|
||||
}
|
||||
|
||||
function loadFn(file, name) {
|
||||
const src = fs.readFileSync(path.join(__dirname, '..', '..', file), 'utf8');
|
||||
return new Function('"use strict";' + extractFn(src, name) + `\nreturn ${name};`)();
|
||||
}
|
||||
|
||||
const labels2D = loadFn('static/highway.js', 'chordHarmonyLabels');
|
||||
const labels3D = loadFn('plugins/highway_3d/screen.js', 'chordHarmonyLabels');
|
||||
|
||||
for (const [name, fn] of [['2D', labels2D], ['3D', labels3D]]) {
|
||||
test(`chordHarmonyLabels (${name}) surfaces rn + voicing`, () => {
|
||||
assert.deepEqual(fn({ rn: 'ii7', q: 'm7', deg: 2 }, 'open'),
|
||||
{ rn: 'ii7', voicing: 'open' });
|
||||
});
|
||||
|
||||
test(`chordHarmonyLabels (${name}) trims whitespace`, () => {
|
||||
assert.deepEqual(fn({ rn: ' V7 ' }, ' drop2 '),
|
||||
{ rn: 'V7', voicing: 'drop2' });
|
||||
});
|
||||
|
||||
test(`chordHarmonyLabels (${name}) empties absent / malformed inputs`, () => {
|
||||
assert.deepEqual(fn(null, undefined), { rn: '', voicing: '' });
|
||||
assert.deepEqual(fn({}, ''), { rn: '', voicing: '' });
|
||||
assert.deepEqual(fn({ rn: 7 }, 7), { rn: '', voicing: '' }); // non-string
|
||||
assert.deepEqual(fn(undefined, 'shell'), { rn: '', voicing: 'shell' });
|
||||
assert.deepEqual(fn({ rn: 'vi' }, null), { rn: 'vi', voicing: '' });
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user