|
@@ -1,314 +1,83 @@ |
|
|
import React, {ChangeEventHandler, FC} from 'react' |
|
|
|
|
|
|
|
|
import React, {FC} from 'react' |
|
|
import * as PropTypes from 'prop-types' |
|
|
import * as PropTypes from 'prop-types' |
|
|
|
|
|
import FretboardString from '../FretboardString/FretboardString' |
|
|
|
|
|
import Fret from '../Fret/Fret' |
|
|
|
|
|
|
|
|
const propTypes = { |
|
|
const propTypes = { |
|
|
tunings: PropTypes.arrayOf(PropTypes.number), |
|
|
|
|
|
|
|
|
strings: PropTypes.number, |
|
|
frets: PropTypes.number, |
|
|
frets: PropTypes.number, |
|
|
leftHanded: PropTypes.bool, |
|
|
|
|
|
|
|
|
mirror: PropTypes.bool, |
|
|
fixedTunings: PropTypes.bool, |
|
|
fixedTunings: PropTypes.bool, |
|
|
displayTunings: PropTypes.bool, |
|
|
displayTunings: PropTypes.bool, |
|
|
capoFret: PropTypes.number, |
|
|
capoFret: PropTypes.number, |
|
|
fretted: PropTypes.arrayOf(PropTypes.number), |
|
|
fretted: PropTypes.arrayOf(PropTypes.number), |
|
|
|
|
|
landmarks: PropTypes.bool, |
|
|
|
|
|
frettedNames: PropTypes.arrayOf(PropTypes.string), |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
export type Props = PropTypes.InferProps<typeof propTypes> |
|
|
export type Props = PropTypes.InferProps<typeof propTypes> |
|
|
|
|
|
|
|
|
const PITCHES = 'C C# D D# E F F# G G# A A# B'.split(' ') |
|
|
|
|
|
|
|
|
|
|
|
const getPitchName = (n: number) => PITCHES[n % 12] + Math.floor(n / 12) |
|
|
|
|
|
|
|
|
|
|
|
const Fretboard: FC<Props> = ({ |
|
|
const Fretboard: FC<Props> = ({ |
|
|
tunings = [64, 59, 55, 50, 45, 40], |
|
|
|
|
|
|
|
|
strings = 6, |
|
|
frets = 24, |
|
|
frets = 24, |
|
|
leftHanded = false, |
|
|
|
|
|
fixedTunings = false, |
|
|
|
|
|
displayTunings = false, |
|
|
|
|
|
|
|
|
mirror = false, |
|
|
capoFret = 0, |
|
|
capoFret = 0, |
|
|
fretted = [], |
|
|
fretted = [], |
|
|
|
|
|
landmarks = false, |
|
|
|
|
|
frettedNames = [], |
|
|
}) => { |
|
|
}) => { |
|
|
const [clientSide, setClientSide, ] = React.useState(false) |
|
|
|
|
|
const [pitches, setPitches, ] = React.useState<string[]>(tunings!.map(t => getPitchName(t!))) |
|
|
|
|
|
|
|
|
|
|
|
const updateStringPitch = (currentStringNumber: number): ChangeEventHandler<HTMLInputElement> => e => { |
|
|
|
|
|
const { value } = e.target |
|
|
|
|
|
setPitches(oldPitches => [ |
|
|
|
|
|
...oldPitches.slice(0, currentStringNumber), |
|
|
|
|
|
getPitchName(Number(value)), |
|
|
|
|
|
...oldPitches.slice(currentStringNumber + 1), |
|
|
|
|
|
]) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
React.useEffect(() => { |
|
|
|
|
|
setClientSide(true) |
|
|
|
|
|
}, []) |
|
|
|
|
|
|
|
|
|
|
|
// React.useEffect(() => { |
|
|
|
|
|
// if (!Array.isArray(tunings!)) { |
|
|
|
|
|
// return |
|
|
|
|
|
// } |
|
|
|
|
|
// |
|
|
|
|
|
// setPitches(() => tunings.map(t => getPitchName(t!))) |
|
|
|
|
|
// }, [tunings]) |
|
|
|
|
|
|
|
|
|
|
|
if (!Array.isArray(tunings!)) { |
|
|
|
|
|
return null |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const strings = tunings!.map(t => new Array(frets).fill(0).map((_, i) => t! + i + 1)) |
|
|
|
|
|
|
|
|
const str: boolean[][] = new Array(strings) |
|
|
|
|
|
.fill(null) |
|
|
|
|
|
.map(() => new Array(frets) |
|
|
|
|
|
.fill(null) |
|
|
|
|
|
.map(() => false) |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
return ( |
|
|
return ( |
|
|
<div |
|
|
<div |
|
|
style={{ |
|
|
style={{ |
|
|
border: '0.0625rem solid', |
|
|
border: '0.0625rem solid', |
|
|
boxSizing: 'border-box', |
|
|
boxSizing: 'border-box', |
|
|
|
|
|
userSelect: 'none', |
|
|
}} |
|
|
}} |
|
|
> |
|
|
> |
|
|
{strings!.map((stringPitches, stringNumber) => ( |
|
|
|
|
|
|
|
|
{str!.map((f, stringNumber: number) => ( |
|
|
<div |
|
|
<div |
|
|
style={{ |
|
|
style={{ |
|
|
display: 'flex', |
|
|
display: 'flex', |
|
|
flexDirection: leftHanded ? 'row-reverse' : 'row', |
|
|
|
|
|
|
|
|
flexDirection: mirror ? 'row-reverse' : 'row', |
|
|
}} |
|
|
}} |
|
|
> |
|
|
> |
|
|
{ |
|
|
|
|
|
displayTunings |
|
|
|
|
|
&& ( |
|
|
|
|
|
<div |
|
|
|
|
|
style={{ |
|
|
|
|
|
display: 'block', |
|
|
|
|
|
width: '5rem', |
|
|
|
|
|
height: '1rem', |
|
|
|
|
|
padding: '0 0 0 1rem', |
|
|
|
|
|
boxSizing: 'border-box', |
|
|
|
|
|
position: 'relative', |
|
|
|
|
|
flexShrink: 0, |
|
|
|
|
|
}} |
|
|
|
|
|
> |
|
|
|
|
|
<div |
|
|
|
|
|
style={{ |
|
|
|
|
|
display: 'block', |
|
|
|
|
|
width: '100%', |
|
|
|
|
|
height: '100%', |
|
|
|
|
|
padding: 0, |
|
|
|
|
|
boxSizing: 'border-box', |
|
|
|
|
|
position: 'relative', |
|
|
|
|
|
}} |
|
|
|
|
|
> |
|
|
|
|
|
<span |
|
|
|
|
|
style={{ |
|
|
|
|
|
display: 'block', |
|
|
|
|
|
width: '2rem', |
|
|
|
|
|
height: '100%', |
|
|
|
|
|
position: 'absolute', |
|
|
|
|
|
top: 0, |
|
|
|
|
|
left: 0, |
|
|
|
|
|
pointerEvents: 'none', |
|
|
|
|
|
}} |
|
|
|
|
|
> |
|
|
|
|
|
{pitches[stringNumber]} |
|
|
|
|
|
</span> |
|
|
|
|
|
<input |
|
|
|
|
|
type="number" |
|
|
|
|
|
style={{ |
|
|
|
|
|
display: 'block', |
|
|
|
|
|
width: '100%', |
|
|
|
|
|
height: '100%', |
|
|
|
|
|
padding: 0, |
|
|
|
|
|
boxSizing: 'border-box', |
|
|
|
|
|
border: 0, |
|
|
|
|
|
backgroundColor: 'transparent', |
|
|
|
|
|
font: 'inherit', |
|
|
|
|
|
textAlign: 'right', |
|
|
|
|
|
}} |
|
|
|
|
|
readOnly={!clientSide || Boolean(fixedTunings)} |
|
|
|
|
|
onChange={updateStringPitch(stringNumber)} |
|
|
|
|
|
defaultValue={tunings[stringNumber]!} |
|
|
|
|
|
min={0} |
|
|
|
|
|
max={127} |
|
|
|
|
|
/> |
|
|
|
|
|
</div> |
|
|
|
|
|
</div> |
|
|
|
|
|
) |
|
|
|
|
|
} |
|
|
|
|
|
<div |
|
|
|
|
|
style={{ |
|
|
|
|
|
display: 'block', |
|
|
|
|
|
position: 'relative', |
|
|
|
|
|
width: '0.5rem', |
|
|
|
|
|
height: '1rem', |
|
|
|
|
|
flexShrink: 0, |
|
|
|
|
|
padding: 0, |
|
|
|
|
|
borderWidth: '0 0.0625rem', |
|
|
|
|
|
borderStyle: 'none solid', |
|
|
|
|
|
}} |
|
|
|
|
|
|
|
|
<Fret |
|
|
|
|
|
fretNumber={0} |
|
|
|
|
|
totalFrets={frets!} |
|
|
|
|
|
firstString={stringNumber === 0} |
|
|
|
|
|
strings={strings!} |
|
|
|
|
|
landmarks={landmarks} |
|
|
|
|
|
fretted={fretted![stringNumber] === 0} |
|
|
|
|
|
capoFret={capoFret} |
|
|
|
|
|
name={frettedNames![stringNumber]} |
|
|
> |
|
|
> |
|
|
<button |
|
|
|
|
|
type="button" |
|
|
|
|
|
style={{ |
|
|
|
|
|
position: 'relative', |
|
|
|
|
|
display: 'block', |
|
|
|
|
|
width: '100%', |
|
|
|
|
|
height: '100%', |
|
|
|
|
|
flexShrink: 0, |
|
|
|
|
|
padding: 0, |
|
|
|
|
|
border: 0, |
|
|
|
|
|
backgroundColor: 'transparent', |
|
|
|
|
|
}} |
|
|
|
|
|
|
|
|
<FretboardString |
|
|
|
|
|
stringNumber={stringNumber} |
|
|
|
|
|
/> |
|
|
|
|
|
</Fret> |
|
|
|
|
|
{f.map((_, i) => ( |
|
|
|
|
|
<Fret |
|
|
|
|
|
fretNumber={i + 1} |
|
|
|
|
|
totalFrets={frets!} |
|
|
|
|
|
firstString={stringNumber === 0} |
|
|
|
|
|
strings={strings!} |
|
|
|
|
|
landmarks={landmarks} |
|
|
|
|
|
fretted={fretted![stringNumber] === i + 1} |
|
|
|
|
|
capoFret={capoFret} |
|
|
|
|
|
name={frettedNames![stringNumber]} |
|
|
> |
|
|
> |
|
|
<span |
|
|
|
|
|
style={{ |
|
|
|
|
|
display: 'block', |
|
|
|
|
|
width: '100%', |
|
|
|
|
|
height: `${0.03125 + stringNumber * 0.03125}rem`, |
|
|
|
|
|
backgroundColor: 'currentColor', |
|
|
|
|
|
}} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<FretboardString |
|
|
|
|
|
stringNumber={stringNumber} |
|
|
/> |
|
|
/> |
|
|
{ |
|
|
|
|
|
fretted![stringNumber] === 0 |
|
|
|
|
|
&& ( |
|
|
|
|
|
<span |
|
|
|
|
|
style={{ |
|
|
|
|
|
position: 'absolute', |
|
|
|
|
|
top: 0, |
|
|
|
|
|
left: 0, |
|
|
|
|
|
height: '100%', |
|
|
|
|
|
width: '100%', |
|
|
|
|
|
display: 'flex', |
|
|
|
|
|
alignItems: 'center', |
|
|
|
|
|
justifyContent: 'space-around', |
|
|
|
|
|
flexDirection: 'column', |
|
|
|
|
|
color: 'red', |
|
|
|
|
|
}} |
|
|
|
|
|
> |
|
|
|
|
|
<span |
|
|
|
|
|
style={{ |
|
|
|
|
|
width: '0.75rem', |
|
|
|
|
|
height: '0.75rem', |
|
|
|
|
|
backgroundColor: 'currentColor', |
|
|
|
|
|
borderRadius: '50%', |
|
|
|
|
|
}} |
|
|
|
|
|
/> |
|
|
|
|
|
</span> |
|
|
|
|
|
) |
|
|
|
|
|
} |
|
|
|
|
|
</button> |
|
|
|
|
|
</div> |
|
|
|
|
|
{stringPitches.map((_, i) => ( |
|
|
|
|
|
<div |
|
|
|
|
|
style={{ |
|
|
|
|
|
width: `${(frets! - i - 1) / 12 * 100 + 100}%`, |
|
|
|
|
|
height: '1rem', |
|
|
|
|
|
position: 'relative', |
|
|
|
|
|
}} |
|
|
|
|
|
> |
|
|
|
|
|
{(i % 12 === 2 || i % 12 === 4 || i % 12 === 6 || i % 12 === 8 || i % 12 === 11) && stringNumber === 0 && ( |
|
|
|
|
|
<div |
|
|
|
|
|
style={{ |
|
|
|
|
|
position: 'absolute', |
|
|
|
|
|
top: 0, |
|
|
|
|
|
height: `${tunings.length * 100}%`, |
|
|
|
|
|
width: '100%', |
|
|
|
|
|
display: 'flex', |
|
|
|
|
|
alignItems: 'center', |
|
|
|
|
|
justifyContent: 'space-around', |
|
|
|
|
|
flexDirection: 'column', |
|
|
|
|
|
}} |
|
|
|
|
|
> |
|
|
|
|
|
<div |
|
|
|
|
|
style={{ |
|
|
|
|
|
width: '0.75rem', |
|
|
|
|
|
height: '0.75rem', |
|
|
|
|
|
backgroundColor: 'currentColor', |
|
|
|
|
|
opacity: 0.5, |
|
|
|
|
|
borderRadius: '50%', |
|
|
|
|
|
}} |
|
|
|
|
|
/> |
|
|
|
|
|
{i % 12 === 11 && ( |
|
|
|
|
|
<div |
|
|
|
|
|
style={{ |
|
|
|
|
|
width: '0.75rem', |
|
|
|
|
|
height: '0.75rem', |
|
|
|
|
|
backgroundColor: 'currentColor', |
|
|
|
|
|
opacity: 0.5, |
|
|
|
|
|
borderRadius: '50%', |
|
|
|
|
|
}} |
|
|
|
|
|
/> |
|
|
|
|
|
)} |
|
|
|
|
|
</div> |
|
|
|
|
|
)} |
|
|
|
|
|
<button |
|
|
|
|
|
key={i} |
|
|
|
|
|
type="button" |
|
|
|
|
|
style={{ |
|
|
|
|
|
position: 'relative', |
|
|
|
|
|
display: 'block', |
|
|
|
|
|
width: '100%', |
|
|
|
|
|
height: '1rem', |
|
|
|
|
|
padding: 0, |
|
|
|
|
|
borderWidth: '0 0.0625rem', |
|
|
|
|
|
borderStyle: 'none solid', |
|
|
|
|
|
backgroundColor: 'transparent', |
|
|
|
|
|
}} |
|
|
|
|
|
> |
|
|
|
|
|
<span |
|
|
|
|
|
style={{ |
|
|
|
|
|
display: 'block', |
|
|
|
|
|
width: '100%', |
|
|
|
|
|
height: `${0.03125 + stringNumber * 0.03125}rem`, |
|
|
|
|
|
backgroundColor: 'currentColor', |
|
|
|
|
|
}} |
|
|
|
|
|
/> |
|
|
|
|
|
{ |
|
|
|
|
|
fretted![stringNumber] === i + 1 |
|
|
|
|
|
&& ( |
|
|
|
|
|
<span |
|
|
|
|
|
style={{ |
|
|
|
|
|
position: 'absolute', |
|
|
|
|
|
top: 0, |
|
|
|
|
|
left: 0, |
|
|
|
|
|
height: '100%', |
|
|
|
|
|
width: '100%', |
|
|
|
|
|
display: 'flex', |
|
|
|
|
|
alignItems: 'center', |
|
|
|
|
|
justifyContent: 'space-around', |
|
|
|
|
|
flexDirection: 'column', |
|
|
|
|
|
color: 'red', |
|
|
|
|
|
}} |
|
|
|
|
|
> |
|
|
|
|
|
<span |
|
|
|
|
|
style={{ |
|
|
|
|
|
width: '0.75rem', |
|
|
|
|
|
height: '0.75rem', |
|
|
|
|
|
backgroundColor: 'currentColor', |
|
|
|
|
|
borderRadius: '50%', |
|
|
|
|
|
}} |
|
|
|
|
|
/> |
|
|
|
|
|
</span> |
|
|
|
|
|
) |
|
|
|
|
|
} |
|
|
|
|
|
</button> |
|
|
|
|
|
{ |
|
|
|
|
|
capoFret === (i + 1) && stringNumber === 0 && ( |
|
|
|
|
|
<div |
|
|
|
|
|
style={{ |
|
|
|
|
|
position: 'absolute', |
|
|
|
|
|
top: 0, |
|
|
|
|
|
height: `${tunings.length * 100}%`, |
|
|
|
|
|
width: '100%', |
|
|
|
|
|
}} |
|
|
|
|
|
> |
|
|
|
|
|
<div |
|
|
|
|
|
style={{ |
|
|
|
|
|
width: '0.75rem', |
|
|
|
|
|
height: '100%', |
|
|
|
|
|
backgroundColor: 'currentColor', |
|
|
|
|
|
borderRadius: '0.375rem', |
|
|
|
|
|
}} |
|
|
|
|
|
/> |
|
|
|
|
|
</div> |
|
|
|
|
|
) |
|
|
|
|
|
} |
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
</Fret> |
|
|
))} |
|
|
))} |
|
|
</div> |
|
|
</div> |
|
|
))} |
|
|
))} |
|
|