function createPiano({
totalWidth = width,
totalHeight = Math.max(totalWidth / 4, 120),
margin = {
top: 10,
right: 10,
left: 10,
bottom: 10
},
minNote = 'C3',
maxNote = 'C6'
} = {}) {
const svg = d3
.create('svg')
.attr('width', totalWidth)
.attr('height', totalHeight);
const allNotes = tonal.Scale.rangeOf('C chromatic')(minNote, maxNote).map(
normalizeNote
);
const whiteKeys = allNotes.filter(isWhiteKey);
const blackKeys = allNotes.filter(isBlackKey);
const boardWidth = totalWidth - margin.left - margin.right;
const blackKeyToWhiteKeyRatio = 3 / 4;
const isFirstNoteBlackKey = isBlackKey(allNotes[0]);
const isLastNoteBlackKey = isBlackKey(allNotes[allNotes.length - 1]);
const numBlackKeysOnEdge = isFirstNoteBlackKey
? isLastNoteBlackKey
? 2
: 1
: 0;
const whiteKeyWidth =
boardWidth /
(whiteKeys.length + (numBlackKeysOnEdge * blackKeyToWhiteKeyRatio) / 2);
const blackKeyWidth = whiteKeyWidth * blackKeyToWhiteKeyRatio;
const whiteKeyToX = d3
.scaleLinear()
.domain([
getOrdinal(whiteKeys[0]),
getOrdinal(whiteKeys[whiteKeys.length - 1])
])
.range([
margin.left + (isFirstNoteBlackKey ? blackKeyWidth / 2 : 0),
totalWidth -
margin.right -
whiteKeyWidth -
(isLastNoteBlackKey ? blackKeyWidth / 2 : 0)
]);
const blackKeyToX = i => whiteKeyToX(i + 1) - blackKeyWidth / 2;
const noteToKey = n => {
const note = normalizeNote(n);
const ordinal = getOrdinal(note);
if (isBlackKey(note)) {
return {
note,
isBlackKey: true,
...elaborateRect(
blackKeyToX(ordinal),
margin.top,
blackKeyWidth,
((totalHeight - margin.bottom - margin.top) * 2) / 3
)
};
} else {
return {
note,
isBlackKey: false,
...elaborateRect(
whiteKeyToX(ordinal),
margin.top,
whiteKeyWidth,
totalHeight - margin.bottom - margin.top
)
};
}
};
const allKeys = allNotes.map(noteToKey).sort(a => (a.isBlackKey ? 1 : -1));
svg
.selectAll('.key')
.data(allKeys)
.enter()
.append('rect')
.attr('class', d => `key ${tonal.Note.get(d.note).name}`)
.attr('x', d => d.x)
.attr('y', d => d.y)
.attr('width', d => d.width)
.attr('height', d => d.height)
.attr('fill', d => (d.isBlackKey ? 'gainsboro' : 'white'))
.attr('stroke', 'gainsboro');
return Object.assign(svg.node(), {
svg,
noteToKey,
minNote,
maxNote
});
}