Hey! I’m loving everything about how lyrics work but I was wondering if there was a way to transcode the format of the chords when viewing them. For example, instead of showing standard chord names like [Em] [C] [G], could there be an option to view them with the Nashville Number system like [6] [4] [1]? I’ve seen a few ChordPro programs implement that and was just wondering if that was a possibility or if that has already been implemented. Thanks again for the help!
For some reason I thought this was possible but I can’t find anything about it.
So I agree! Would love this feature!
Editing to add some features that would be great.
First:
I have a track on each song that shows the key of the song. It’s just simple a long clip that I put “A, B, etc.” in to display the key of the song. It would be great to be able to put in the Nashville Number system and be able to reference the “Key” to set the chords if band members want to see the actual chords rather than the numbers.
Second:
We have run into problems where the acoustic guitar player is playing Song 1 with a capo but no capo for the third song. It would be great to be able to add a track that the chord track could reference where you could specify what needs to be transposed but it could be done per song rather than per track.
The more I think through it, the more complicated it could get depending on who needs what transposed to what key (EG1 capo 5, Acoustic capo 2) but something along these lines would be great if possible. Thank you!
Okay! So after figuring a few things out with ChatGPT, I was able to come up with some code for the styles.css and a javascript to help everything work better. Honestly, I don’t really understand the full logistics of it but things are working so far. The only thing I’ve added is that I use a different font that I had downloaded called CMG Sans.
For what I use I just input numbers instead of chords and have a separate lyrics, letter chords, and a numbers track.
The other thing that this script does is superscript specific elements of the chord name to make it easier to read for both numbers and letters.
Hopefully this helps someone else!
Here’s the style.css that I’m using
/*
* Custom CSS Styles for AbleSet, modify at your own risk.
* These styles can be overridden by project-specific styles.
* Images and other files in this folder can be accessed
* via /custom-styles/ in your styles.
* Learn more at: https://ableset.app/docs/custom-styles/
*/
@font-face {
font-family: "CMG Sans";
src: url("CMGSans-Bold.ttf") format("truetype");
font-weight: 400;
font-style: normal;
}
/** Section Bar Size **/
.lyrics .section h3.clickable {
/* UNLOCKED section labels text sizing */
font-size: calc(var(--ui-size) * var(--font-adjust) * 3);
line-height: 1;
font-weight: 600;
}
.lyrics .section h3 {
/* LOCKED section labels text sizing */
font-size: calc(var(--ui-size) * var(--font-adjust) * 3);
line-height: 1;
font-weight: 600;
/* badge box behavior */
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.2em 0.5em; /* <- controls pill thickness/width */
border-radius: 9999px; /* <- makes it a rounded pill/circle */
min-height: 1.4em; /* <- keeps it from getting too squished */
}
/** Lyrics Font Size **/
.lyrics .lyrics-line p {
line-height: .75; /* Keeps spacing tight for stage lyrics */
}
/* Tighten spacing between regular lyric lines */
.lyrics .lyrics-line p {
margin-top: 0;
margin-bottom: -.75em; /* pulls next line (like chords) upward */
line-height: 1.0;
}
/* Pull chord lines upward toward the lyric line above */
.lyrics .lyrics-line:has(.chord) {
margin-top: -0.3em;
}
/** Lyric View Fonts*/
.lyrics-page {
font-family: Georgia, "Times New Roman", Times, serif;
}
.performance {
font-family: Georgia, "Times New Roman", Times, serif;
}
.setlist {
font-family: Georgia, "Times New Roman", Times, serif;
}
/** Different font for Chords, Nashville needs javascript **/
.chord,
.nashvillechord {
font-family: "CMG Sans", -apple-system, sans-serif;
font-weight: 400;
letter-spacing: 0.02em;
}
/** use a different font for flats/sharps **/
.chord sup .accidental,
.nashvillechord sup .accidental {
font-family: "Noto Music", "Arial Unicode MS", "Segoe UI Symbol", sans-serif;
/* Keep progress bar height normal */
.lyrics .section {
height: var(--line-height); /* or whatever the original value was */
}
/** fix spacing for accidentals **/
.chord sup .accidental,
.nashvillechord sup .accidental {
margin-left: -0.9em;
margin-right: -0.1em;
letter-spacing: -0.1em;
display: inline-block; /* keeps spacing consistent */
}
/** Lower the slash slightly for better alignment **/
.chord .slash,
.nashvillechord .slash {
display: inline-block;
margin-left: -.05em;
margin-right: 0em;
line-height: 1;
position: relative;
top: -.05em; /* Adjust between 0.1em–0.25em for perfect vertical centering */
}
and here’s the javascript I’m using
(function () {
// ========= Utilities =========
function toPrettyAcc(ch) {
if (ch === 'b') return '<span class="accidental">♭</span>';
if (ch === '#') return '<span class="accidental">♯</span>';
return ch;
}
function prettifyAccString(str) {
if (!str) return "";
let out = "";
for (let i = 0; i < str.length; i++) {
out += toPrettyAcc(str[i]);
}
return out;
}
// Pulls off "maj" / "min" / "m" / "dim" / "aug" from start
function extractQualityPrefix(str) {
if (!str) return { quality: "", rest: "" };
const qualities = ["maj", "min", "dim", "aug", "m"];
for (let q of qualities) {
if (str.startsWith(q)) {
return { quality: q, rest: str.slice(q.length) };
}
}
return { quality: "", rest: str };
}
// Fallback renderer: just superscript accidentals where they appear,
// everything else baseline. Used for weird/unexpected shapes.
function basicAccidentalSupRender(str) {
let out = "";
for (let i = 0; i < str.length; i++) {
const ch = str[i];
if (ch === 'b' || ch === '#') {
out += `<sup>${toPrettyAcc(ch)}</sup>`;
} else {
out += ch;
}
}
return out;
}
// ========= Letter chord logic =========
//
// Letter chord rules:
// F#m7 -> F<sup>♯</sup>m<sup>7</sup>
// Bbmaj7 -> B<sup>♭</sup>maj<sup>7</sup>
// Eb2 -> E<sup>♭</sup><sup>2</sup>
// A/C# -> A / C<sup>♯</sup> (we'll tighten "/" with .slash span)
//
function renderLetterChord(raw) {
if (!raw) return "";
// Split once on slash
let mainPart = raw;
let slashPart = "";
const slashIdx = raw.indexOf("/");
if (slashIdx !== -1) {
mainPart = raw.slice(0, slashIdx);
slashPart = raw.slice(slashIdx + 1); // after '/'
}
// MAIN side
const mainMatch = mainPart.match(/^([A-Ga-g])(.*)$/);
if (!mainMatch) {
// fallback if it's something unexpected
return basicAccidentalSupRender(raw);
}
const rootLetter = mainMatch[1]; // "F"
const afterRoot = mainMatch[2]; // "#m7", "bmaj7", "b2", "", etc.
// pull accidental(s) right after root
let i = 0;
let leadingAcc = "";
while (i < afterRoot.length && (afterRoot[i] === 'b' || afterRoot[i] === '#')) {
leadingAcc += afterRoot[i];
i++;
}
const afterAcc = afterRoot.slice(i);
const { quality, rest } = extractQualityPrefix(afterAcc);
// Build left side html:
// rootLetter (baseline)
// leadingAcc (superscript, pretty)
// quality (baseline)
// rest (superscript, pretty)
let leftHTML = "";
leftHTML += rootLetter;
if (leadingAcc) {
leftHTML += `<sup>${prettifyAccString(leadingAcc)}</sup>`;
}
if (quality) {
leftHTML += quality;
}
if (rest) {
leftHTML += `<sup>${prettifyAccString(rest)}</sup>`;
}
// SLASH side (bass note), if any
if (slashPart) {
leftHTML += `<span class="slash">/</span>` + renderLetterChordBass(slashPart);
}
return leftHTML;
}
function renderLetterChordBass(rawBass) {
// bass like "C#", "C#m7", "Bb", etc.
const m = rawBass.match(/^([A-Ga-g])(.*)$/);
if (!m) {
return basicAccidentalSupRender(rawBass);
}
const bassRoot = m[1];
const after = m[2]; // "#m7", "bmaj7", etc.
let j = 0;
let bassAcc = "";
while (j < after.length && (after[j] === 'b' || after[j] === '#')) {
bassAcc += after[j];
j++;
}
const afterBassAcc = after.slice(j);
const { quality, rest } = extractQualityPrefix(afterBassAcc);
let bassHTML = "";
bassHTML += bassRoot;
if (bassAcc) {
bassHTML += `<sup>${prettifyAccString(bassAcc)}</sup>`;
}
if (quality) {
bassHTML += quality;
}
if (rest) {
bassHTML += `<sup>${prettifyAccString(rest)}</sup>`;
}
return bassHTML;
}
// ========= Nashville chord logic =========
//
// Nashville examples:
// b7 -> <sup>♭</sup>7
// 1b7 -> 1<sup>♭7</sup>
// #4/5 -> <sup>♯</sup>4/5
// b4min7/6 -> <sup>♭</sup>4min<sup>7</sup>/6
// 4#11/3 -> 4<sup>♯11</sup>/3
//
// Rules:
// - Accidentals before the number go superscript before the number.
// - The base number (1–7) is baseline.
// - Quality after the number (min/maj/m/etc) is baseline.
// - Extensions (7, 11, add9, #11, b7...) go superscript.
// - Slash bass: apply same logic to the bass side.
//
function renderNashvilleChord(raw) {
if (!raw) return "";
let mainPart = raw;
let slashPart = "";
const slashIdx = raw.indexOf("/");
if (slashIdx !== -1) {
mainPart = raw.slice(0, slashIdx);
slashPart = raw.slice(slashIdx + 1);
}
const leftHTML = renderNashvilleOneSide(mainPart);
if (slashPart) {
return leftHTML + `<span class="slash">/</span>` + renderNashvilleOneSide(slashPart);
} else {
return leftHTML;
}
}
function renderNashvilleOneSide(str) {
// Case 1: starts with accidental(s) then number. e.g. "b4min7", "#4", "b7"
let mLead = str.match(/^([b#]+)([1-7])(.*)$/);
// Case 2: starts with number. e.g. "1b7", "4#11", "6min7"
let mPlain = str.match(/^([1-7])(.*)$/);
let leadingAcc = "";
let baseNum = "";
let restAfter = "";
if (mLead) {
leadingAcc = mLead[1]; // "b", "#", "bb" (rare)
baseNum = mLead[2]; // "4", "7"
restAfter = mLead[3]; // "min7", ""
} else if (mPlain) {
leadingAcc = "";
baseNum = mPlain[1]; // "1"
restAfter = mPlain[2]; // "b7", "#11", "min7", etc.
} else {
// doesn't look Nashville, fallback
return basicAccidentalSupRender(str);
}
// peel accidental(s) immediately after the base number (like 1b7 -> afterBaseAcc="b")
let i = 0;
let afterBaseAcc = "";
while (i < restAfter.length && (restAfter[i] === 'b' || restAfter[i] === '#')) {
afterBaseAcc += restAfter[i];
i++;
}
const afterAcc = restAfter.slice(i);
// split off quality (min/maj/m/dim/aug) from what's left
const { quality, rest } = extractQualityPrefix(afterAcc);
// Build HTML for this side:
// leadingAcc -> superscript (♭ / ♯ before the number)
// baseNum -> baseline
// afterBaseAcc -> superscript (like the "b" in 1b7)
// quality -> baseline
// rest (extensions) -> superscript
let html = "";
if (leadingAcc) {
html += `<sup>${prettifyAccString(leadingAcc)}</sup>`;
}
html += baseNum;
if (afterBaseAcc) {
html += `<sup>${prettifyAccString(afterBaseAcc)}</sup>`;
}
if (quality) {
html += quality;
}
if (rest) {
html += `<sup>${prettifyAccString(rest)}</sup>`;
}
return html;
}
// ========= Auto-detect chord type and apply =========
//
// Nashville-like if:
// - starts with b/# followed by a number ("b7", "#4", "b4min7")
// - OR starts with a number "1", "2", ... ("1b7", "4#11/3")
//
function isNashvilleLike(txt) {
return (
/^[b#][1-7]/.test(txt) || // b7, #4, b4min7, etc.
/^[1-7]/.test(txt) // 1b7, 4#11/3, etc.
);
}
function formatChordText(raw) {
if (isNashvilleLike(raw)) {
return renderNashvilleChord(raw);
} else {
return renderLetterChord(raw);
}
}
function transformChords() {
document.querySelectorAll('.chord').forEach(node => {
if (node.dataset.supApplied === "1") return;
const raw = node.textContent.trim();
if (!raw) return;
node.innerHTML = formatChordText(raw);
node.dataset.supApplied = "1";
});
}
// Run once now
transformChords();
// Re-run if AbleSet updates DOM
const observer = new MutationObserver(transformChords);
observer.observe(document.body, { subtree: true, childList: true });
})();


