|
@@ -1,17 +1,55 @@ |
|
|
import React, { FC } from 'react' |
|
|
|
|
|
|
|
|
import React, {ChangeEventHandler, FC} from 'react' |
|
|
import * as PropTypes from 'prop-types' |
|
|
import * as PropTypes from 'prop-types' |
|
|
|
|
|
|
|
|
const propTypes = { |
|
|
const propTypes = { |
|
|
tunings: PropTypes.arrayOf(PropTypes.number), |
|
|
tunings: PropTypes.arrayOf(PropTypes.number), |
|
|
frets: PropTypes.number, |
|
|
frets: PropTypes.number, |
|
|
leftHanded: PropTypes.bool, |
|
|
leftHanded: PropTypes.bool, |
|
|
|
|
|
fixedTunings: PropTypes.bool, |
|
|
|
|
|
displayTunings: PropTypes.bool, |
|
|
|
|
|
capoFret: PropTypes.number, |
|
|
|
|
|
fretted: PropTypes.arrayOf(PropTypes.number), |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
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 PITCHES = 'C C# D D# E F F# G G# A A# B'.split(' ') |
|
|
|
|
|
|
|
|
const Fretboard: FC<Props> = ({ tunings = [64, 59, 55, 50, 45, 40], frets = 24, leftHanded = false }) => { |
|
|
|
|
|
|
|
|
const getPitchName = (n: number) => PITCHES[n % 12] + Math.floor(n / 12) |
|
|
|
|
|
|
|
|
|
|
|
const Fretboard: FC<Props> = ({ |
|
|
|
|
|
tunings = [64, 59, 55, 50, 45, 40], |
|
|
|
|
|
frets = 24, |
|
|
|
|
|
leftHanded = false, |
|
|
|
|
|
fixedTunings = false, |
|
|
|
|
|
displayTunings = false, |
|
|
|
|
|
capoFret = 0, |
|
|
|
|
|
fretted = [], |
|
|
|
|
|
}) => { |
|
|
|
|
|
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!)) { |
|
|
if (!Array.isArray(tunings!)) { |
|
|
return null |
|
|
return null |
|
|
} |
|
|
} |
|
@@ -23,59 +61,80 @@ const Fretboard: FC<Props> = ({ tunings = [64, 59, 55, 50, 45, 40], frets = 24, |
|
|
style={{ |
|
|
style={{ |
|
|
border: '0.0625rem solid', |
|
|
border: '0.0625rem solid', |
|
|
boxSizing: 'border-box', |
|
|
boxSizing: 'border-box', |
|
|
transform: leftHanded |
|
|
|
|
|
? 'perspective(6rem) rotateY(2deg) scaleX(1.375)' |
|
|
|
|
|
: 'perspective(6rem) rotateY(-2deg) scaleX(1.375)', |
|
|
|
|
|
transformOrigin: leftHanded ? 'left' : 'right', |
|
|
|
|
|
}} |
|
|
}} |
|
|
> |
|
|
> |
|
|
{strings!.map((pitches, stringNumber) => ( |
|
|
|
|
|
|
|
|
{strings!.map((stringPitches, stringNumber) => ( |
|
|
<div |
|
|
<div |
|
|
style={{ |
|
|
style={{ |
|
|
display: 'flex', |
|
|
display: 'flex', |
|
|
flexDirection: leftHanded ? 'row-reverse' : 'row', |
|
|
flexDirection: leftHanded ? 'row-reverse' : 'row', |
|
|
}} |
|
|
}} |
|
|
> |
|
|
> |
|
|
<div |
|
|
|
|
|
style={{ |
|
|
|
|
|
display: 'flex', |
|
|
|
|
|
flexDirection: leftHanded ? 'row-reverse' : 'row', |
|
|
|
|
|
width: '5rem', |
|
|
|
|
|
height: '1rem', |
|
|
|
|
|
padding: 0, |
|
|
|
|
|
boxSizing: 'border-box', |
|
|
|
|
|
}} |
|
|
|
|
|
> |
|
|
|
|
|
<input |
|
|
|
|
|
type="number" |
|
|
|
|
|
style={{ |
|
|
|
|
|
display: 'block', |
|
|
|
|
|
width: '3rem', |
|
|
|
|
|
height: '1rem', |
|
|
|
|
|
padding: 0, |
|
|
|
|
|
boxSizing: 'border-box', |
|
|
|
|
|
}} |
|
|
|
|
|
defaultValue={tunings[stringNumber]!} |
|
|
|
|
|
min={0} |
|
|
|
|
|
max={127} |
|
|
|
|
|
/> |
|
|
|
|
|
<span |
|
|
|
|
|
style={{ |
|
|
|
|
|
display: 'block', |
|
|
|
|
|
width: '2rem', |
|
|
|
|
|
height: '1rem', |
|
|
|
|
|
textAlign: leftHanded ? 'left' : 'right', |
|
|
|
|
|
}} |
|
|
|
|
|
> |
|
|
|
|
|
{PITCHES[tunings[stringNumber]! % 12]} |
|
|
|
|
|
{Math.floor(tunings[stringNumber]! / 12)} |
|
|
|
|
|
</span> |
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
{ |
|
|
|
|
|
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 |
|
|
<div |
|
|
style={{ |
|
|
style={{ |
|
|
display: 'block', |
|
|
display: 'block', |
|
|
position: 'relative', |
|
|
position: 'relative', |
|
|
width: '1rem', |
|
|
|
|
|
|
|
|
width: '0.5rem', |
|
|
height: '1rem', |
|
|
height: '1rem', |
|
|
flexShrink: 0, |
|
|
flexShrink: 0, |
|
|
padding: 0, |
|
|
padding: 0, |
|
@@ -88,8 +147,8 @@ const Fretboard: FC<Props> = ({ tunings = [64, 59, 55, 50, 45, 40], frets = 24, |
|
|
style={{ |
|
|
style={{ |
|
|
position: 'relative', |
|
|
position: 'relative', |
|
|
display: 'block', |
|
|
display: 'block', |
|
|
width: '1rem', |
|
|
|
|
|
height: '1rem', |
|
|
|
|
|
|
|
|
width: '100%', |
|
|
|
|
|
height: '100%', |
|
|
flexShrink: 0, |
|
|
flexShrink: 0, |
|
|
padding: 0, |
|
|
padding: 0, |
|
|
border: 0, |
|
|
border: 0, |
|
@@ -104,12 +163,40 @@ const Fretboard: FC<Props> = ({ tunings = [64, 59, 55, 50, 45, 40], frets = 24, |
|
|
backgroundColor: 'currentColor', |
|
|
backgroundColor: 'currentColor', |
|
|
}} |
|
|
}} |
|
|
/> |
|
|
/> |
|
|
|
|
|
{ |
|
|
|
|
|
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> |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
{pitches.map((_, i) => ( |
|
|
|
|
|
|
|
|
{stringPitches.map((_, i) => ( |
|
|
<div |
|
|
<div |
|
|
style={{ |
|
|
style={{ |
|
|
width: `${(frets! - i + frets! / 2) * 100}%`, |
|
|
|
|
|
|
|
|
width: `${(frets! - i - 1) / 12 * 100 + 100}%`, |
|
|
height: '1rem', |
|
|
height: '1rem', |
|
|
position: 'relative', |
|
|
position: 'relative', |
|
|
}} |
|
|
}} |
|
@@ -129,8 +216,8 @@ const Fretboard: FC<Props> = ({ tunings = [64, 59, 55, 50, 45, 40], frets = 24, |
|
|
> |
|
|
> |
|
|
<div |
|
|
<div |
|
|
style={{ |
|
|
style={{ |
|
|
width: `${0.75 - i * (1 / 80)}rem`, |
|
|
|
|
|
height: `${0.75 - i * (1 / 256)}rem`, |
|
|
|
|
|
|
|
|
width: '0.75rem', |
|
|
|
|
|
height: '0.75rem', |
|
|
backgroundColor: 'currentColor', |
|
|
backgroundColor: 'currentColor', |
|
|
opacity: 0.5, |
|
|
opacity: 0.5, |
|
|
borderRadius: '50%', |
|
|
borderRadius: '50%', |
|
@@ -139,8 +226,8 @@ const Fretboard: FC<Props> = ({ tunings = [64, 59, 55, 50, 45, 40], frets = 24, |
|
|
{i % 12 === 11 && ( |
|
|
{i % 12 === 11 && ( |
|
|
<div |
|
|
<div |
|
|
style={{ |
|
|
style={{ |
|
|
width: `${0.75 - i * (1 / 80)}rem`, |
|
|
|
|
|
height: `${0.75 - i * (1 / 256)}rem`, |
|
|
|
|
|
|
|
|
width: '0.75rem', |
|
|
|
|
|
height: '0.75rem', |
|
|
backgroundColor: 'currentColor', |
|
|
backgroundColor: 'currentColor', |
|
|
opacity: 0.5, |
|
|
opacity: 0.5, |
|
|
borderRadius: '50%', |
|
|
borderRadius: '50%', |
|
@@ -171,7 +258,56 @@ const Fretboard: FC<Props> = ({ tunings = [64, 59, 55, 50, 45, 40], frets = 24, |
|
|
backgroundColor: 'currentColor', |
|
|
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> |
|
|
</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> |
|
|
</div> |
|
|
))} |
|
|
))} |
|
|
</div> |
|
|
</div> |
|
|