Skip to content

Commit

Permalink
Merge pull request #26 from buzcarter/feature/UGS2-12__guitar_tabs
Browse files Browse the repository at this point in the history
Feature/ugs2 12  guitar tabs
  • Loading branch information
buzcarter authored Sep 13, 2023
2 parents 9291817 + b8c3076 commit 8b496c7
Show file tree
Hide file tree
Showing 8 changed files with 243 additions and 111 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@ukegeeks/song-formatter",
"version": "0.0.2-alpha-1",
"version": "0.0.3-alpha",
"description": "Convert ChordPro markup text to HTML with chord fingering diagrams",
"main": "./dist/song-formatter.bundle.js",
"type": "module",
Expand Down
39 changes: 39 additions & 0 deletions src/js/cpmImporter/__tests__/data/tabs.data.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const toArry = (text) => text.split('\n');

const TestData = {
'Basic Tabs (Happy Path)': {
tuning: ['G', 'C', 'E', 'A'],
block: ''
+ '-----2---2-----------3-3---' + LineBreak
+ '---3---3---3-------0-------' + LineBreak
Expand Down Expand Up @@ -33,6 +34,7 @@ const TestData = {
tabStrings: [],
},
'Tabs with Measures (Happy Path)': {
tuning: ['G', 'C', 'E', 'A'],
block: ''
+ '|-----2---2-----|-------3-3---' + LineBreak
+ '|---3---3---3---|-----0-------' + LineBreak
Expand Down Expand Up @@ -60,6 +62,43 @@ const TestData = {
],
tabStrings: [],
},
'Guitar (Happy Path**)': {
tuning: ['E', 'A', 'D', 'G', 'B', 'E'],
block: ''
+ 'E--12-10-9----------9-10-9----------9-----9------------|' + LineBreak
+ 'B----------66-10-12--------18-10-12---33----12--10-----|' + LineBreak
+ 'G------------------------------------------------------|' + LineBreak
+ 'D---------------------------2--------------------------|' + LineBreak
+ 'A------------------------1-----------------------------|' + LineBreak
+ 'E-------------------5--2-------------------------------|' + LineBreak,
frets: [
['12', '10', '9', '9', '10', '9', '9', '9'],
['66', '10', '12', '18', '10', '12', '33', '12', '10'],
[],
['2'],
['1'],
['5', '2'],
],
guide: '- *- *-*- *- *- *-*- *-*- *- *- *-*- *- *- *- *- |',
minLength: 56,
symbols: [
'E---*--*-*----------*--*-*----------*-----*------------|',
'B-----------*--*--*---------*--*--*----*-----*---*-----|',
'G------------------------------------------------------|',
'D---------------------------*--------------------------|',
'A------------------------*-----------------------------|',
'E-------------------*--*-------------------------------|',
],
tabs: [
['-', '12', '-', '10', '-', '9', '-', '-', '-', '-', '-', '-', '-', '9', '-', '10', '-', '9', '-', '-', '-', '-', '-', '-', '-', '9', '-', '-', '-', '9', '-', '-', '-', '-', '-', '|'],
['-', '-', '-', '-', '-', '-', '-', '66', '-', '10', '-', '12', '-', '-', '-', '-', '-', '-', '-', '18', '-', '10', '-', '12', '-', '-', '-', '33', '-', '-', '-', '12', '-', '10', '-', '|'],
['-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '|'],
['-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '2', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '|'],
['-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '1', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '|'],
['-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '5', '-', '2', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '|'],
],
tabStrings: [],
},
};

Object.keys(TestData)
Expand Down
28 changes: 18 additions & 10 deletions src/js/cpmImporter/__tests__/tabs.test.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { settings } from '../../configs';
import TestData from './data/tabs.data';
const { __test__ } = require('../tabs');

describe('chordBrush', () => {
describe('tabs', () => {
describe('getFretNumbers', () => {
const { getFretNumbers } = __test__;
Object.keys(TestData)
.forEach((name) => {
const { frets, tabStrings } = TestData[name];
const { tuning, frets, tabStrings } = TestData[name];
it(`should parse values for ${name} correctly`, () => {
settings.tuning = tuning;
const result = getFretNumbers(tabStrings);
expect(result).toEqual(frets);
});
Expand All @@ -18,8 +20,9 @@ describe('chordBrush', () => {
const { getGuideLine } = __test__;
Object.keys(TestData)
.forEach((name) => {
const { guide, symbols, minLength } = TestData[name];
it(`should summarize ${name} correctly`, () => {
const { tuning, guide, symbols, minLength } = TestData[name];
it(`should generate Guide ${name} correctly (${tuning})`, () => {
settings.tuning = tuning;
const result = getGuideLine(symbols, minLength);
expect(result).toEqual(guide);
});
Expand All @@ -30,8 +33,9 @@ describe('chordBrush', () => {
const { getMinLineLength } = __test__;
Object.keys(TestData)
.forEach((name) => {
const { minLength, tabStrings } = TestData[name];
const { tuning, minLength, tabStrings } = TestData[name];
it(`should calculate shortest required length ${name} correctly`, () => {
settings.tuning = tuning;
const result = getMinLineLength(tabStrings);
expect(result).toEqual(minLength);
});
Expand All @@ -42,10 +46,13 @@ describe('chordBrush', () => {
const { getPackedLines } = __test__;
Object.keys(TestData)
.forEach((name) => {
const { frets, symbols, guide, minLength, tabs } = TestData[name];
it(`should complete tabs for ${name} correctly`, () => {
const result = getPackedLines(frets, symbols, guide, minLength);
expect(result).toEqual(tabs);
const { tuning, frets, symbols, guide, minLength, tabs } = TestData[name];
settings.tuning = tuning;
const result = getPackedLines(frets, symbols, guide, minLength);
result.forEach((expectedLine, lineNbr) => {
it(`should complete tabs for ${name} (${lineNbr}) correctly`, () => {
expect(expectedLine).toEqual(tabs[lineNbr]);
});
});
});
});
Expand All @@ -54,8 +61,9 @@ describe('chordBrush', () => {
const { getSymbols } = __test__;
Object.keys(TestData)
.forEach((name) => {
const { symbols, tabStrings } = TestData[name];
const { tuning, symbols, tabStrings } = TestData[name];
it(`should tokenize ${name} correctly`, () => {
settings.tuning = tuning;
const result = getSymbols(tabStrings);
expect(result).toEqual(symbols);
});
Expand Down
131 changes: 68 additions & 63 deletions src/js/cpmImporter/tabs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,29 @@
* Creates "packed" versions of the tabs, including a "key line" that's comprised
* only of '-' and '*' -- the asterisks denoting where a dot will eventually be placed.
*/

import { StringArray, ExpandedTabs, TabBlock } from './interfaces/SongBlock';
import { BlockTypes } from './interfaces/BlockTypesEnum';

import { getLastStringName, getNumStrings } from '../configs';
import { BlockTypes } from './interfaces/BlockTypesEnum';

const RegExes = Object.freeze({
/* eslint-disable key-spacing */
INT: /(\d+)/g,

TWO_DIGITS: /(\d{2})/g,
ONE_DIGIT: /(\d)/g,

DOUBLE_DASH: /--/g,
SINGLE_DASH: / -/g,
TRAILING_DASH: /-+$/g,
/* eslint-enable key-spacing */
});

/**
* This is insanely long, insanely kludgy, but, insanely, it works. This will read break a block of text into
* four lines (the ukulele strings), then find which frets are used by each. Then, the hard part, pack un-needed
* dashes. Once it's done that a 2-dimentional array (strings X frets) is created and returned.
* @param tabStrings {array<string>} Block of tablbabure to be parsed
* @return {2-dimentional array}
* @param tabStrings Block of tablbabure to be parsed
*/
export function readTabs(tabStrings: StringArray): TabBlock {
const hasLabels = tabStrings[getNumStrings() - 1][0] === getLastStringName();
Expand All @@ -37,9 +48,10 @@ export function readTabs(tabStrings: StringArray): TabBlock {
* Processes tabStrings stripping the first character from each line
*/
function stripStringLabels(tabStrings: StringArray): void {
for (let i = 0; i < getNumStrings(); i++) {
tabStrings[i] = tabStrings[i].substr(1);
}
tabStrings
.forEach((string, i) => {
tabStrings[i] = string.substr(1);
});
}

/**
Expand All @@ -48,11 +60,9 @@ function stripStringLabels(tabStrings: StringArray): void {
* in use, for each line.
*/
function getFretNumbers(tabStrings: StringArray): StringArray[] {
// first, get the frets
const integerRegEx = /([0-9]+)/g;
const frets = [];
for (let i = 0; i < getNumStrings(); i++) {
frets[i] = tabStrings[i].match(integerRegEx) || [];
frets[i] = tabStrings[i].match(RegExes.INT) || [];
}
return frets;
}
Expand All @@ -62,18 +72,14 @@ function getFretNumbers(tabStrings: StringArray): StringArray[] {
* This helps us pack because "12" and "7" now occupy the same space horizontally.
*/
function getSymbols(tabStrings: StringArray): StringArray {
// convert to symbols
const twoDigitRegEx = /([0-9]{2})/g;
const singleDigitRegex = /([0-9])/g;
const symbols = [];

// TODO: verify why using getNumStrings() instead of tabStrings.length (appears in other methods, again, do you recall why?)
for (let i = 0; i < getNumStrings(); i++) {
symbols[i] = tabStrings[i]
.replace(twoDigitRegEx, '-*')
.replace(singleDigitRegex, '*');
}
return symbols;
return tabStrings
.slice(0, getNumStrings())
.reduce((symbols: StringArray, sym) => {
symbols.push(sym
.replace(RegExes.TWO_DIGITS, '-*')
.replace(RegExes.ONE_DIGIT, '*'));
return symbols;
}, []);
}

/**
Expand All @@ -82,16 +88,15 @@ function getSymbols(tabStrings: StringArray): StringArray {
* this gets a TODO: get max!
*/
function getMinLineLength(tabStrings: StringArray): number {
let minLength = 0;
const trailingDashesRegEx = /-+$/gi;

for (let i = 0; i < tabStrings.length; i++) {
const line = tabStrings[i].trim().replace(trailingDashesRegEx, '');
if (line.length > minLength) {
minLength = line.length;
}
}
return minLength;
return tabStrings
.slice(0, getNumStrings())
.reduce((minLength, line) => {
line = line.trim().replace(RegExes.TRAILING_DASH, '');
if (line.length > minLength) {
minLength = line.length;
}
return minLength;
}, 0);
}

/**
Expand All @@ -107,17 +112,15 @@ function getGuideLine(symbols: StringArray, minLength: number): string {
if (symbols[0][i] === '|') {
guide += '|';
} else {
// TODO: assumes 4 strings, use getNumStrings()
guide += ((symbols[0][i] === '*') || (symbols[1][i] === '*') || (symbols[2][i] === '*') || (symbols[3][i] === '*')) ? '*' : '-';
guide += symbols.some((sym) => sym[i] === '*') ? '*' : '-';
}
}
const doubleDashRegEx = /--/g;
guide = guide.replace(doubleDashRegEx, '- ');
const singleDashReg = / -/g;

guide = guide.replace(RegExes.DOUBLE_DASH, '- ');
let lastGuide = guide;
// eslint-disable-next-line no-constant-condition
while (true) {
guide = guide.replace(singleDashReg, ' ');
guide = guide.replace(RegExes.SINGLE_DASH, ' ');
if (guide === lastGuide) {
break;
}
Expand All @@ -133,33 +136,35 @@ function getGuideLine(symbols: StringArray, minLength: number): string {
* a "12" occupies no more horizontal space than a "5".
*/
function getPackedLines(frets: StringArray[], symbols: StringArray, guide: string, minLength: number): ExpandedTabs {
// pack it!
const packed: ExpandedTabs = [];
const packed = Array(getNumStrings()).fill('*').map((): string[] => []);
packed
.forEach((string, stringIdx) => {
// index to single line within packed array (along a string)
let lineIdx = 0;

for (let stringIdx = 0; stringIdx < getNumStrings(); stringIdx++) {
packed.push([]);
}
// fret marker counter
let fretCount = 0;

guide
.split('')
.slice(0, minLength)
.forEach((char, guideIdx) => {
if (char === ' ') {
return;
}
// a temp variable to hold the 'note'
let chrNote = '';
if (symbols[stringIdx][guideIdx] === '*') {
chrNote = frets[stringIdx][fretCount];
fretCount++;
} else {
chrNote = char === '|' ? '|' : '-';
}
packed[stringIdx][lineIdx] = chrNote;
lineIdx++;
});
});

for (let stringIdx = 0; stringIdx < getNumStrings(); stringIdx++) { // loop over lines
// index to single line within packed array (along a string)
let lineIdx = 0;
// fret marker counter
let fretCount = 0;
for (let guideIdx = 0; guideIdx < minLength; guideIdx++) { // loop over guide
if (guide[guideIdx] !== ' ') {
// a temp variable to hold the 'note'
let chrNote = '';
if (symbols[stringIdx][guideIdx] === '*') {
chrNote = frets[stringIdx][fretCount];
fretCount++;
} else {
chrNote = ((guide[guideIdx] === '|')) ? '|' : '-';
}
packed[stringIdx][lineIdx] = chrNote;
lineIdx++;
}
}
}
return packed;
}

Expand Down
Loading

0 comments on commit 8b496c7

Please sign in to comment.