Skip to content

Commit

Permalink
Further chord picker functionality and story
Browse files Browse the repository at this point in the history
  • Loading branch information
RyanButton committed Aug 9, 2022
1 parent 8085642 commit eebe030
Show file tree
Hide file tree
Showing 6 changed files with 3,992 additions and 4,101 deletions.
14 changes: 8 additions & 6 deletions packages/react-guitar-fretter/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,15 @@
"lodash.flatten": "^4.4.0",
"lodash.max": "^4.0.1",
"lodash.min": "^4.0.1",
"lodash.range": "^3.2.0"
"lodash.range": "^3.2.0",
"lodash.reverse": "^4.0.1"
},
"devDependencies": {
"@types/lodash.flatmap": "^4.5.7",
"@types/lodash.flatten": "^4.4.7",
"@types/lodash.max": "^4.0.7",
"@types/lodash.min": "^4.0.7",
"@types/lodash.range": "^3.2.7"
"@types/lodash.flatmap": "^4.5.6",
"@types/lodash.flatten": "^4.4.6",
"@types/lodash.max": "^4.0.6",
"@types/lodash.min": "^4.0.6",
"@types/lodash.range": "^3.2.6",
"@types/lodash.reverse": "^4.0.7"
}
}
103 changes: 99 additions & 4 deletions packages/react-guitar-fretter/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,49 @@ import flatten from 'lodash.flatten'
import flatMap from 'lodash.flatmap'
import max from 'lodash.max'
import min from 'lodash.min'
import reverse from 'lodash.reverse'
import mod from './util/mod'
import search from './util/search'
import findDuplicates from './util/find-duplicates'

/**
* Returns an array of string frettings.
*
* @param chord
* `root` is starting position in the chromatic scale, starting at 0 which is C for standard tuning.
*
* `semitones` is an array of booleans that represent the notes played in the chord on the chromatic scale.
*
* For example, to describe a c-major chord, true must be set for the 1st, 5th, and 8th note of the chromatic scale i.e:
* ```javascript
* const chord = {
root: 0,
semitones: [
true,
false,
false,
false,
true,
false,
false,
true,
false,
false,
false,
false,
],
}
* ```
* @param options
* Optional configuration of `tuning`, `frets`, and `restrictChordTypeTo`.
*/
export default function fretter(
chord: { root: number; semitones: boolean[] },
options?: { tuning?: number[]; frets?: number }
options?: {
tuning?: number[]
frets?: number
restrictChordTypeTo?: 'open' | 'barre' | 'all'
}
): number[][] {
const { root, semitones } = chord
const { tuning = [64, 59, 55, 50, 45, 40], frets = 22 } = options ?? {}
Expand Down Expand Up @@ -41,13 +78,34 @@ export default function fretter(
)
)

return search<number[]>(
const isOpenChord = (fretting: number[]) => {
return fretting.includes(0) && !fretting.some((fret) => fret > 4)
}

const isBarreChord = (fretting: number[]) => {
const strippedFretting = fretting.filter((fret) => fret > 0)
const duplicates = findDuplicates(strippedFretting)
return (
duplicates.length !== 0 &&
duplicates.some(
(duplicateFret) =>
duplicateFret === min(fretting.filter((fret) => fret >= 0))
)
)
}

const playableChords = (fretting: number[]) => {
const strippedFretting = fretting.filter((fret) => fret > 0)
const duplicates = findDuplicates(strippedFretting)
return strippedFretting.length < 5 || (duplicates && isBarreChord(fretting))
}

const frettings = search<number[]>(
[],
(fretting) =>
fretting.length === 0
? flatten(
range(tuning.length)
.map((string) => tuning.length - 1 - string)
reverse(range(tuning.length))
.map((string) => getFrets(string, [root]))
.map((frets, i) =>
frets.map((fret) =>
Expand All @@ -62,9 +120,46 @@ export default function fretter(
)
.map((fretting) => [...fretting].reverse())
.filter(containsAllNotes)
.filter(playableChords)
.sort(
(f1, f2) =>
(min(f1.filter((n) => n > 0)) ?? 0) -
(min(f2.filter((n) => n > 0)) ?? 0)
)

if (options?.restrictChordTypeTo === 'open')
return frettings.filter(isOpenChord)
if (options?.restrictChordTypeTo === 'barre')
return frettings.filter(isBarreChord)
return frettings
}

/**
* Converts a string of form '00010010000' to array of booleans where 0 -> false and 1 -> true.
*
* @param text
* String of the form '00010010000'
*/
export const toSemitones = (text: string) =>
text.split('').map((c) => c === '1')

/**
* Returns semitone boolean array for use in guitar fretter package.
*
* @param chordType
* 'major' | 'minor' | 'diminished triad'
*/
export const getChordSemitones = (
chordType: 'major' | 'minor' | 'diminished triad'
) => {
switch (chordType.toLowerCase()) {
case 'major':
return toSemitones('00010010000')
case 'minor':
return toSemitones('00100010000')
case 'diminished triad':
return toSemitones('00100100000')
default:
return toSemitones('0000000000')
}
}
13 changes: 13 additions & 0 deletions packages/react-guitar-fretter/src/util/find-duplicates.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const findDuplicates = (arr: number[]) => {
let sorted_arr = arr.slice().sort()

let results = []
for (let i = 0; i < sorted_arr.length - 1; i++) {
if (sorted_arr[i + 1] == sorted_arr[i]) {
results.push(sorted_arr[i])
}
}
return results
}

export default findDuplicates
59 changes: 48 additions & 11 deletions storybook/index.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,28 @@ import range from 'lodash.range'
import { useState } from '@storybook/addons'
import coco from 'react-guitar-theme-coco'
import dark from 'react-guitar-theme-dark'
import fretter, { getChordSemitones } from 'react-guitar-fretter'

const themes = { spanish: spanishTheme, dark, coco }

const getNotes = () => {
return range(12)
.map((note) => note + 12)
.reduce(
(acc, note) => ({
...acc,
[midiToNoteName(note, { pitchClass: true, sharps: true })]: note,
}),
{} as {
[K: string]: number
}
)
}

storiesOf('Guitar', module)
.addDecorator(withKnobs)
.add('advanced', () => {
const notes = range(12)
.map((note) => note + 12)
.reduce(
(acc, note) => ({
...acc,
[midiToNoteName(note, { pitchClass: true, sharps: true })]: note,
}),
{} as {
[K: string]: number
}
)
const notes = getNotes()
const root = select('Root', notes, notes['C'])
const renderFingerFunctions = {
'Scientific Pitch Notation': getRenderFingerSpn(standard),
Expand Down Expand Up @@ -143,3 +148,35 @@ storiesOf('Guitar', module)
<Guitar strings={[0, 1, 2, 2, 0, -1]} />
</div>
))
.add('fretter', () => {
const notes = getNotes()
const chords = fretter(
{
root: select('Root', notes, notes['C']),
semitones: getChordSemitones(
select('Type', ['major', 'minor', 'diminished triad'], 'major')
),
},
{
restrictChordTypeTo: select(
'Fretting type',
['all', 'open', 'barre'],
'all'
),
}
)
return (
<div style={{ fontSize: '.5em' }}>
{chords.map((chord) => (
<Guitar
strings={chord}
renderFinger={getRenderFingerSpn(standard)}
center={true}
/>
))}
{chords.length === 0 && (
<div style={{ fontSize: '3em' }}>No chords found!</div>
)}
</div>
)
})
3 changes: 2 additions & 1 deletion storybook/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"react-guitar-sound": "^1.1.0",
"react-guitar-theme-coco": "^1.1.1",
"react-guitar-theme-dark": "^1.1.1",
"react-guitar-tunings": "^1.1.0"
"react-guitar-tunings": "^1.1.0",
"react-guitar-fretter": "^1.0.3"
}
}
Loading

0 comments on commit eebe030

Please sign in to comment.