vexchords
Version:
Javascript Chord Charts
253 lines (213 loc) • 7.6 kB
JavaScript
/*
* Vex Chords v2
* Mohit Muthanna Cheppudira -- http://0xfe.blogspot.com
*/
import { SVG } from '@svgdotjs/svg.js';
// ChordBox implements the rendering logic for the chord
// diagrams.
class ChordBox {
// sel can be a selector or an element.
constructor(sel, params) {
this.sel = sel;
this.params = {
...{
numStrings: 6,
numFrets: 5,
x: 0,
y: 0,
width: 100,
height: 120,
strokeWidth: 1,
showTuning: true,
defaultColor: '#666',
bgColor: '#fff',
labelColor: '#fff',
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"',
fontSize: undefined,
fontStyle: 'light',
fontWeight: '100',
labelWeight: '100',
},
...params,
};
// Setup defaults if not specifically overridden
['bridgeColor', 'stringColor', 'fretColor', 'strokeColor', 'textColor'].forEach((param) => {
this.params[param] = this.params[param] || this.params.defaultColor;
});
['stringWidth', 'fretWidth'].forEach((param) => {
this.params[param] = this.params[param] || this.params.strokeWidth;
});
// Create canvas and add it to the DOM
this.canvas = SVG()
.addTo(sel)
.size(this.params.width, this.params.height);
// Size and shift board
this.width = this.params.width * 0.75;
this.height = this.params.height * 0.75;
this.x = this.params.x + this.params.width * 0.15;
this.y = this.params.y + this.params.height * 0.15;
this.numStrings = this.params.numStrings;
this.numFrets = this.params.numFrets;
this.spacing = this.width / this.numStrings;
this.fretSpacing = this.height / (this.numFrets + 2);
// Add room on sides for finger positions on 1. and 6. string
this.x += this.spacing / 2;
this.y += this.fretSpacing;
this.metrics = {
circleRadius: this.width / 20,
barreRadius: this.width / 25,
fontSize: this.params.fontSize || Math.ceil(this.width / 8),
barShiftX: this.width / 28,
bridgeStrokeWidth: Math.ceil(this.height / 36),
};
// Content
this.position = 0;
this.positionText = 0;
this.chord = [];
this.bars = [];
this.tuning = ['E', 'A', 'D', 'G', 'B', 'E'];
}
setNumFrets(numFrets) {
this.numFrets = numFrets;
this.fretSpacing = this.height / (this.numFrets + 1);
return this;
}
setPositionText(position) {
this.positionText = position;
return this;
}
drawText(x, y, msg, attrs) {
const textAttrs = {
...{
family: this.params.fontFamily,
size: this.metrics.fontSize,
style: this.params.fontStyle,
weight: this.params.fontWeight,
},
...attrs,
};
const text = this.canvas
.text(`${msg}`)
.stroke(this.params.textColor)
.fill(this.params.textColor)
.font(textAttrs);
return text.move(x - text.length() / 2, y);
}
drawLine(x, y, newX, newY) {
return this.canvas.line(0, 0, newX - x, newY - y).move(x, y);
}
draw({
chord, position, barres, positionText, tuning,
}) {
this.chord = chord;
this.position = position || 0;
this.positionText = positionText || 0;
this.barres = barres || [];
this.tuning = tuning || ['E', 'A', 'D', 'G', 'B', 'E'];
if (this.tuning.length === 0) {
this.fretSpacing = this.height / (this.numFrets + 1);
}
const { spacing } = this;
const { fretSpacing } = this;
// Draw guitar bridge
if (this.position <= 1) {
const fromX = this.x;
const fromY = this.y - this.metrics.bridgeStrokeWidth;
this.canvas
.rect(this.x + spacing * (this.numStrings - 1) - fromX, this.y - fromY)
.move(fromX, fromY)
.stroke({ width: 0 })
.fill(this.params.bridgeColor);
} else {
// Draw position number
this.drawText(this.x - this.spacing / 2 - this.spacing * 0.1, this.y + this.fretSpacing * this.positionText, this.position);
}
// Draw strings
for (let i = 0; i < this.numStrings; i += 1) {
this.drawLine(this.x + spacing * i, this.y, this.x + spacing * i, this.y + fretSpacing * this.numFrets).stroke({
width: this.params.stringWidth,
color: this.params.stringColor,
});
}
// Draw frets
for (let i = 0; i < this.numFrets + 1; i += 1) {
this.drawLine(this.x, this.y + fretSpacing * i, this.x + spacing * (this.numStrings - 1), this.y + fretSpacing * i).stroke({
width: this.params.fretWidth,
color: this.params.fretColor,
});
}
// Draw tuning keys
if (this.params.showTuning && this.tuning.length !== 0) {
for (let i = 0; i < Math.min(this.numStrings, this.tuning.length); i += 1) {
this.drawText(this.x + this.spacing * i, this.y + this.numFrets * this.fretSpacing + this.fretSpacing / 12, this.tuning[i]);
}
}
// Draw chord
for (let i = 0; i < this.chord.length; i += 1) {
// Light up string, fret, and optional label.
this.lightUp({
string: this.chord[i][0],
fret: this.chord[i][1],
label: this.chord.length > 2 ? this.chord[i][2] : undefined,
});
}
// Draw barres
for (let i = 0; i < this.barres.length; i += 1) {
this.lightBar(this.barres[i].fromString, this.barres[i].toString, this.barres[i].fret);
}
}
lightUp({ string, fret, label }) {
const stringNum = this.numStrings - string;
const shiftPosition = this.position === 1 && this.positionText === 1 ? this.positionText : 0;
const mute = fret === 'x';
const fretNum = fret === 'x' ? 0 : fret - shiftPosition;
const x = this.x + this.spacing * stringNum;
let y = this.y + this.fretSpacing * fretNum;
if (fretNum === 0) {
y -= this.metrics.bridgeStrokeWidth;
}
if (!mute) {
this.canvas
.circle()
.move(x, y - this.fretSpacing / 2)
.radius(this.metrics.circleRadius)
.stroke({ color: this.params.strokeColor, width: this.params.strokeWidth })
.fill(fretNum > 0 ? this.params.strokeColor : this.params.bgColor);
} else {
this.drawText(x, y - this.fretSpacing, 'X');
}
if (label) {
const fontSize = this.metrics.fontSize * 0.55;
const textYShift = fontSize * 0.66;
this.drawText(x, y - this.fretSpacing / 2 - textYShift, label, {
weight: this.params.labelWeight,
size: fontSize,
})
.stroke({
width: 0.7,
color: fretNum !== 0 ? this.params.labelColor : this.params.strokeColor,
})
.fill(fretNum !== 0 ? this.params.labelColor : this.params.strokeColor);
}
return this;
}
lightBar(stringFrom, stringTo, theFretNum) {
let fretNum = theFretNum;
if (this.position === 1 && this.positionText === 1) {
fretNum -= this.positionText;
}
const stringFromNum = this.numStrings - stringFrom;
const stringToNum = this.numStrings - stringTo;
const x = this.x + this.spacing * stringFromNum - this.metrics.barShiftX;
const xTo = this.x + this.spacing * stringToNum + this.metrics.barShiftX;
const y = this.y + this.fretSpacing * (fretNum - 1) + this.fretSpacing / 4;
const yTo = this.y + this.fretSpacing * (fretNum - 1) + (this.fretSpacing / 4) * 3;
this.canvas
.rect(xTo - x, yTo - y)
.move(x, y)
.radius(this.metrics.barreRadius)
.fill(this.params.strokeColor);
return this;
}
}
export default ChordBox;