Add vertical node positioning by frequency in Path Navigator
This commit is contained in:
parent
27d34fdafc
commit
6924c85d19
|
|
@ -245,10 +245,11 @@
|
|||
const hs1 = pitch1.hs_array;
|
||||
const hs2 = pitch2.hs_array;
|
||||
|
||||
// Check if they differ by ±1 in exactly one dimension (ignoring dim 0)
|
||||
let diffCount = 0;
|
||||
let diffDim = -1;
|
||||
|
||||
for (let i = 1; i < hs1.length; i++) { // Skip dimension 0 (prime 2)
|
||||
for (let i = 1; i < hs1.length; i++) {
|
||||
const diff = hs2[i] - hs1[i];
|
||||
if (Math.abs(diff) === 1) {
|
||||
diffCount++;
|
||||
|
|
@ -261,11 +262,24 @@
|
|||
// Must differ by exactly ±1 in exactly one dimension
|
||||
if (diffCount !== 1) return null;
|
||||
|
||||
// Check dimension 0 (prime 2) - must be same
|
||||
if (hs1[0] !== hs2[0]) return null;
|
||||
// Calculate frequency ratio from pitch difference (hs1 - hs2)
|
||||
// Using the formula from pitch.py:
|
||||
// numerator *= dims[i] ** diff[i] for diff[i] >= 0
|
||||
// denominator *= dims[i] ** (-diff[i]) for diff[i] < 0
|
||||
const dims = [2, 3, 5, 7];
|
||||
let numerator = 1;
|
||||
let denominator = 1;
|
||||
|
||||
// Calculate ratio
|
||||
const ratio = parseFraction(pitch1.fraction) / parseFraction(pitch2.fraction);
|
||||
for (let i = 0; i < hs1.length; i++) {
|
||||
const diff = hs1[i] - hs2[i]; // pitch1 - pitch2
|
||||
if (diff > 0) {
|
||||
numerator *= Math.pow(dims[i], diff);
|
||||
} else if (diff < 0) {
|
||||
denominator *= Math.pow(dims[i], -diff);
|
||||
}
|
||||
}
|
||||
|
||||
const ratio = numerator + "/" + denominator;
|
||||
|
||||
return {
|
||||
dim: diffDim,
|
||||
|
|
@ -335,25 +349,47 @@
|
|||
}
|
||||
|
||||
function buildGraph(chord) {
|
||||
const nodes = chord.map((pitch, i) => ({
|
||||
id: i,
|
||||
pitch: pitch,
|
||||
label: pitch.fraction,
|
||||
hs: pitch.hs_array
|
||||
}));
|
||||
// Calculate cents for each pitch
|
||||
const centsData = chord.map((pitch, i) => {
|
||||
const fr = parseFraction(pitch.fraction);
|
||||
const cents = 1200 * Math.log2(fr);
|
||||
return { i, cents };
|
||||
});
|
||||
|
||||
// Find cents for node 0 (voice 0) - use as bottom reference
|
||||
const node0Cents = centsData.find(d => d.i === 0)?.cents || 0;
|
||||
const maxCents = Math.max(...centsData.map(d => d.cents));
|
||||
const range = maxCents - node0Cents || 1;
|
||||
|
||||
// Create nodes with y position: node 0 at bottom, others relative to it
|
||||
const nodes = chord.map((pitch, i) => {
|
||||
const fr = parseFraction(pitch.fraction);
|
||||
const cents = 1200 * Math.log2(fr);
|
||||
// Map to screen: top = high cents, bottom = node 0
|
||||
const y = height * 0.1 + (height * 0.8) * (1 - (cents - node0Cents) / range);
|
||||
// Initial x position: spread horizontally
|
||||
const x = width * 0.2 + (width * 0.6) * (i / (chord.length - 1 || 1));
|
||||
return {
|
||||
id: i,
|
||||
pitch: pitch,
|
||||
label: String(i),
|
||||
cents: cents.toFixed(0),
|
||||
hs: pitch.hs_array,
|
||||
x: x, // Initial x
|
||||
y: y // Initial y (will be fixed by forceY)
|
||||
};
|
||||
});
|
||||
|
||||
const links = [];
|
||||
const labelSet = new Set();
|
||||
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
for (let j = i + 1; j < nodes.length; j++) {
|
||||
const edgeInfo = getEdgeInfo(nodes[i].pitch, nodes[j].pitch);
|
||||
if (edgeInfo) {
|
||||
const label = formatRatio(edgeInfo.ratio);
|
||||
links.push({
|
||||
source: i,
|
||||
target: j,
|
||||
label: label
|
||||
label: edgeInfo.ratio
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -378,11 +414,11 @@
|
|||
return;
|
||||
}
|
||||
|
||||
// Force simulation
|
||||
// Force simulation with fixed y positions
|
||||
simulation = d3.forceSimulation(nodes)
|
||||
.force("link", d3.forceLink(links).id(d => d.id).distance(100))
|
||||
.force("charge", d3.forceManyBody().strength(-300))
|
||||
.force("center", d3.forceCenter(width / 2, height / 2))
|
||||
.force("y", d3.forceY(d => d.y).strength(1)) // Fix y position
|
||||
.force("collision", d3.forceCollide().radius(40));
|
||||
|
||||
// Draw links
|
||||
|
|
@ -419,7 +455,7 @@
|
|||
node.append("text")
|
||||
.attr("class", "node-label")
|
||||
.attr("dy", 4)
|
||||
.text(d => d.label);
|
||||
.text(d => d.cents);
|
||||
|
||||
// Update positions on tick
|
||||
simulation.on("tick", () => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue