Fretboard component written in React.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

186 lines
5.5 KiB

  1. import React, { FC } from 'react'
  2. import * as PropTypes from 'prop-types'
  3. const propTypes = {
  4. tunings: PropTypes.arrayOf(PropTypes.number),
  5. frets: PropTypes.number,
  6. leftHanded: PropTypes.bool,
  7. }
  8. export type Props = PropTypes.InferProps<typeof propTypes>
  9. const PITCHES = 'C C# D D# E F F# G G# A A# B'.split(' ')
  10. const Fretboard: FC<Props> = ({ tunings = [64, 59, 55, 50, 45, 40], frets = 24, leftHanded = false }) => {
  11. if (!Array.isArray(tunings!)) {
  12. return null
  13. }
  14. const strings = tunings!.map(t => new Array(frets).fill(0).map((_, i) => t! + i + 1))
  15. return (
  16. <div
  17. style={{
  18. border: '0.0625rem solid',
  19. boxSizing: 'border-box',
  20. transform: leftHanded
  21. ? 'perspective(6rem) rotateY(2deg) scaleX(1.375)'
  22. : 'perspective(6rem) rotateY(-2deg) scaleX(1.375)',
  23. transformOrigin: leftHanded ? 'left' : 'right',
  24. }}
  25. >
  26. {strings!.map((pitches, stringNumber) => (
  27. <div
  28. style={{
  29. display: 'flex',
  30. flexDirection: leftHanded ? 'row-reverse' : 'row',
  31. }}
  32. >
  33. <div
  34. style={{
  35. display: 'flex',
  36. flexDirection: leftHanded ? 'row-reverse' : 'row',
  37. width: '5rem',
  38. height: '1rem',
  39. padding: 0,
  40. boxSizing: 'border-box',
  41. }}
  42. >
  43. <input
  44. type="number"
  45. style={{
  46. display: 'block',
  47. width: '3rem',
  48. height: '1rem',
  49. padding: 0,
  50. boxSizing: 'border-box',
  51. }}
  52. defaultValue={tunings[stringNumber]!}
  53. min={0}
  54. max={127}
  55. />
  56. <span
  57. style={{
  58. display: 'block',
  59. width: '2rem',
  60. height: '1rem',
  61. textAlign: leftHanded ? 'left' : 'right',
  62. }}
  63. >
  64. {PITCHES[tunings[stringNumber]! % 12]}
  65. {Math.floor(tunings[stringNumber]! / 12)}
  66. </span>
  67. </div>
  68. <div
  69. style={{
  70. display: 'block',
  71. position: 'relative',
  72. width: '1rem',
  73. height: '1rem',
  74. flexShrink: 0,
  75. padding: 0,
  76. borderWidth: '0 0.0625rem',
  77. borderStyle: 'none solid',
  78. }}
  79. >
  80. <button
  81. type="button"
  82. style={{
  83. position: 'relative',
  84. display: 'block',
  85. width: '1rem',
  86. height: '1rem',
  87. flexShrink: 0,
  88. padding: 0,
  89. border: 0,
  90. backgroundColor: 'transparent',
  91. }}
  92. >
  93. <span
  94. style={{
  95. display: 'block',
  96. width: '100%',
  97. height: `${0.03125 + stringNumber * 0.03125}rem`,
  98. backgroundColor: 'currentColor',
  99. }}
  100. />
  101. </button>
  102. </div>
  103. {pitches.map((_, i) => (
  104. <div
  105. style={{
  106. width: `${(frets! - i + frets! / 2) * 100}%`,
  107. height: '1rem',
  108. position: 'relative',
  109. }}
  110. >
  111. {(i % 12 === 2 || i % 12 === 4 || i % 12 === 6 || i % 12 === 8 || i % 12 === 11) && stringNumber === 0 && (
  112. <div
  113. style={{
  114. position: 'absolute',
  115. top: 0,
  116. height: `${tunings.length * 100}%`,
  117. width: '100%',
  118. display: 'flex',
  119. alignItems: 'center',
  120. justifyContent: 'space-around',
  121. flexDirection: 'column',
  122. }}
  123. >
  124. <div
  125. style={{
  126. width: `${0.75 - i * (1 / 80)}rem`,
  127. height: `${0.75 - i * (1 / 256)}rem`,
  128. backgroundColor: 'currentColor',
  129. opacity: 0.5,
  130. borderRadius: '50%',
  131. }}
  132. />
  133. {i % 12 === 11 && (
  134. <div
  135. style={{
  136. width: `${0.75 - i * (1 / 80)}rem`,
  137. height: `${0.75 - i * (1 / 256)}rem`,
  138. backgroundColor: 'currentColor',
  139. opacity: 0.5,
  140. borderRadius: '50%',
  141. }}
  142. />
  143. )}
  144. </div>
  145. )}
  146. <button
  147. key={i}
  148. type="button"
  149. style={{
  150. position: 'relative',
  151. display: 'block',
  152. width: '100%',
  153. height: '1rem',
  154. padding: 0,
  155. borderWidth: '0 0.0625rem',
  156. borderStyle: 'none solid',
  157. backgroundColor: 'transparent',
  158. }}
  159. >
  160. <span
  161. style={{
  162. display: 'block',
  163. width: '100%',
  164. height: `${0.03125 + stringNumber * 0.03125}rem`,
  165. backgroundColor: 'currentColor',
  166. }}
  167. />
  168. </button>
  169. </div>
  170. ))}
  171. </div>
  172. ))}
  173. </div>
  174. )
  175. }
  176. Fretboard.propTypes = propTypes
  177. export default Fretboard