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.
 
 
 
 

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