Musical keyboard 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.
 
 
 
 

247 lines
5.8 KiB

  1. import * as React from 'react'
  2. import * as PropTypes from 'prop-types'
  3. import StyledAccidentalKey from '../StyledAccidentalKey/StyledAccidentalKey'
  4. import StyledNaturalKey from '../StyledNaturalKey/StyledNaturalKey'
  5. import Keyboard, { propTypes } from './Keyboard'
  6. import KeyboardMap from '../KeyboardMap/KeyboardMap'
  7. const Wrapper: React.FC = (props) => (
  8. <div
  9. {...props}
  10. style={{
  11. // @ts-ignore
  12. '--color-channel-0': '#f55',
  13. '--color-channel-1': '#ff0',
  14. '--color-channel-2': '#0a0',
  15. '--color-channel-3': '#05a',
  16. '--color-channel-4': '#a0f',
  17. '--color-channel-5': '#a00',
  18. '--color-channel-6': '#a50',
  19. '--color-channel-7': '#fa0',
  20. '--color-channel-8': '#0f0',
  21. '--color-channel-9': '#0aa',
  22. '--color-channel-10': '#0ff',
  23. '--color-channel-11': '#f0a',
  24. '--color-channel-12': '#aa0',
  25. '--color-channel-13': '#550',
  26. '--color-channel-14': '#50a',
  27. '--color-channel-15': '#f5f',
  28. }}
  29. />
  30. )
  31. export default {
  32. title: 'Keyboard',
  33. }
  34. type Props = PropTypes.InferProps<typeof propTypes>
  35. // By passing optional props to this story, you can control the props of the component when
  36. // you consume the story in a test.
  37. export const Default = (props?: Partial<Props>) => (
  38. <Wrapper>
  39. <Keyboard {...props} startKey={21} endKey={108} />
  40. </Wrapper>
  41. )
  42. export const WithActiveKeys = (props?: Partial<Props>) => (
  43. <Wrapper>
  44. <Keyboard
  45. {...props}
  46. startKey={21}
  47. endKey={108}
  48. keyChannels={[
  49. {
  50. channel: 0,
  51. key: 60,
  52. velocity: 1,
  53. },
  54. {
  55. channel: 0,
  56. key: 64,
  57. velocity: 1,
  58. },
  59. {
  60. channel: 0,
  61. key: 67,
  62. velocity: 1,
  63. },
  64. ]}
  65. />
  66. </Wrapper>
  67. )
  68. export const WithDifferentKeyRange = (props?: Partial<Props>) => (
  69. <Wrapper>
  70. <Keyboard {...props} height={300} startKey={48} endKey={71} />
  71. </Wrapper>
  72. )
  73. export const Styled = (props?: Partial<Props>) => (
  74. <Wrapper>
  75. <Keyboard
  76. {...props}
  77. startKey={21}
  78. endKey={108}
  79. keyChannels={[
  80. {
  81. channel: 0,
  82. key: 60,
  83. velocity: 1,
  84. },
  85. {
  86. channel: 0,
  87. key: 63,
  88. velocity: 1,
  89. },
  90. {
  91. channel: 0,
  92. key: 67,
  93. velocity: 1,
  94. },
  95. ]}
  96. keyComponents={{
  97. natural: StyledNaturalKey,
  98. accidental: StyledAccidentalKey,
  99. }}
  100. />
  101. </Wrapper>
  102. )
  103. export const AnotherStyled = (props?: Partial<Props>) => (
  104. <div
  105. style={{
  106. // @ts-ignore
  107. '--size-scale-factor': 2,
  108. }}
  109. >
  110. <Wrapper>
  111. <Keyboard
  112. {...props}
  113. startKey={21}
  114. endKey={108}
  115. keyChannels={[
  116. {
  117. channel: 0,
  118. key: 60,
  119. velocity: 1,
  120. },
  121. {
  122. channel: 0,
  123. key: 63,
  124. velocity: 1,
  125. },
  126. {
  127. channel: 0,
  128. key: 67,
  129. velocity: 1,
  130. },
  131. ]}
  132. keyComponents={{
  133. natural: StyledNaturalKey,
  134. accidental: StyledAccidentalKey,
  135. }}
  136. />
  137. </Wrapper>
  138. </div>
  139. )
  140. const HasMapComponent = () => {
  141. const [keyChannels, setKeyChannels] = React.useState<{ key: number; velocity: number; channel: number }[]>([])
  142. const midiAccess = React.useRef<any>(undefined)
  143. const handleKeyOn = (newKeys: { key: number; velocity: number; channel: number; id: number }[]) => {
  144. setKeyChannels((oldKeys) => {
  145. const oldKeysKeys = oldKeys.map((k) => k.key)
  146. const newKeysKeys = newKeys.map((k) => k.key)
  147. const keysOff = oldKeys
  148. .filter((ok) => !newKeysKeys.includes(ok.key))
  149. .map((k) => ({
  150. ...k,
  151. velocity: k.velocity > 1 ? 1 : k.velocity < 0 ? 0 : k.velocity,
  152. }))
  153. const keysOn = newKeys
  154. .filter((nk) => !oldKeysKeys.includes(nk.key))
  155. .map((k) => ({
  156. ...k,
  157. velocity: k.velocity > 1 ? 1 : k.velocity < 0 ? 0 : k.velocity,
  158. }))
  159. keysOn.forEach((k) => {
  160. midiAccess.current?.send([0b10010000 + k.channel, k.key, Math.floor(k.velocity * 127)])
  161. })
  162. keysOff.forEach((k) => {
  163. midiAccess.current?.send([0b10000000 + k.channel, k.key, Math.floor(k.velocity * 127)])
  164. })
  165. return newKeys
  166. })
  167. }
  168. React.useEffect(() => {
  169. const { navigator: maybeNavigator } = window
  170. const navigator = maybeNavigator as Navigator & {
  171. requestMIDIAccess: () => Promise<{ outputs: Map<string, unknown> }>
  172. }
  173. if ('requestMIDIAccess' in navigator) {
  174. navigator.requestMIDIAccess().then((m) => {
  175. midiAccess.current = Array.from(m.outputs.values())[0]
  176. })
  177. }
  178. }, [])
  179. return (
  180. <Wrapper>
  181. <Keyboard hasMap startKey={21} endKey={108} keyChannels={keyChannels}>
  182. <KeyboardMap
  183. channel={0}
  184. onChange={handleKeyOn}
  185. keyboardMapping={{
  186. KeyQ: 60,
  187. Digit2: 61,
  188. KeyW: 62,
  189. Digit3: 63,
  190. KeyE: 64,
  191. KeyR: 65,
  192. Digit5: 66,
  193. KeyT: 67,
  194. Digit6: 68,
  195. KeyY: 69,
  196. Digit7: 70,
  197. KeyU: 71,
  198. KeyI: 72,
  199. Digit9: 73,
  200. KeyO: 74,
  201. Digit0: 75,
  202. KeyP: 76,
  203. BracketLeft: 77,
  204. Equal: 78,
  205. BracketRight: 79,
  206. KeyZ: 48,
  207. KeyS: 49,
  208. KeyX: 50,
  209. KeyD: 51,
  210. KeyC: 52,
  211. KeyV: 53,
  212. KeyG: 54,
  213. KeyB: 55,
  214. KeyH: 56,
  215. KeyN: 57,
  216. KeyJ: 58,
  217. KeyM: 59,
  218. Comma: 60,
  219. KeyL: 61,
  220. Period: 62,
  221. Semicolon: 63,
  222. Slash: 64,
  223. }}
  224. />
  225. </Keyboard>
  226. </Wrapper>
  227. )
  228. }
  229. export const HasMap = () => <HasMapComponent />