Musical keyboard component written in React.
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. import * as React from 'react'
  2. import ReactDOM from 'react-dom'
  3. import Keyboard from '../src'
  4. import * as Channel from './controllers/Channel'
  5. import * as Instrument from './controllers/Instrument'
  6. import * as Generator from './controllers/Generator'
  7. import keyboardMapping from './services/keyboardMapping'
  8. import SoundGenerator from './services/SoundGenerator'
  9. const App = () => {
  10. const [channel, setChannel] = React.useState(0)
  11. const [keyChannels, setKeyChannels] = React.useState<{ key: number; velocity: number; channel: number }[]>([])
  12. const [instruments, setInstruments, ] = React.useState<string[]>([])
  13. const [instrument, setInstrument] = React.useState(0)
  14. const [inputs, setInputs] = React.useState<any[]>([])
  15. const [input, setInput] = React.useState<number>()
  16. const generator = React.useRef<SoundGenerator | undefined>(undefined)
  17. const scrollRef = React.useRef<HTMLDivElement>(null)
  18. const midiInputRef = React.useRef<any>(null)
  19. React.useEffect(() => {
  20. if (!generator.current) {
  21. return
  22. }
  23. Instrument.reflect({ generator: generator.current, channel, instrument })
  24. }, [channel, instrument])
  25. React.useEffect(() => {
  26. Generator
  27. .load()
  28. .then(g => {
  29. Instrument.initialize({ setInstruments, generator: generator.current = g, })
  30. })
  31. }, [])
  32. React.useEffect(() => {
  33. const { current } = scrollRef
  34. if (current) {
  35. current.scrollLeft = current.scrollWidth * 0.4668
  36. }
  37. }, [scrollRef])
  38. React.useEffect(() => {
  39. const loadMIDIInputs = async () => {
  40. const access = await navigator.requestMIDIAccess()
  41. const inputs = Array.from(access.inputs.entries()).map(([handle, input]) => ({
  42. handle,
  43. input,
  44. }))
  45. midiInputRef.current = inputs[0].input
  46. setInputs(inputs)
  47. if (inputs.length > 0) {
  48. setInput(0)
  49. }
  50. }
  51. loadMIDIInputs()
  52. }, [])
  53. React.useEffect(() => {
  54. const theInput = inputs[input]
  55. const handleMidiMessage = (e: any) => {
  56. const arg0 = e.data[0]
  57. const arg1 = e.data[1]
  58. const arg2 = e.data[2]
  59. const type = arg0 & 0b11110000
  60. if (type === 0b10010000 || type === 0b10000000) {
  61. return
  62. }
  63. if (generator.current! && 'sendMessage' in generator.current!) {
  64. generator.current!.sendMessage!(arg0 & 0b00001111, arg0 & 0b11110000, arg1, arg2)
  65. }
  66. }
  67. if (theInput) {
  68. theInput.input.addEventListener('midimessage', handleMidiMessage)
  69. }
  70. return () => {
  71. if (theInput) {
  72. theInput.input.removeEventListener('midimessage', handleMidiMessage)
  73. }
  74. }
  75. }, [inputs, input])
  76. return (
  77. <React.Fragment>
  78. <input
  79. type="number"
  80. id="channel"
  81. min={0}
  82. max={15}
  83. onChange={Channel.change({ setChannel, })}
  84. defaultValue={0}
  85. />
  86. <select
  87. id="instrument"
  88. onChange={Instrument.change({ setInstrument, })}
  89. defaultValue={0}
  90. >
  91. {Array.isArray(instruments) && instruments.map((name, i) => (
  92. <option
  93. key={i}
  94. value={i}
  95. >
  96. {name}
  97. </option>
  98. ))}
  99. </select>
  100. <div
  101. id="keyboard"
  102. ref={scrollRef}
  103. >
  104. <div
  105. id="keyboard-scroll"
  106. >
  107. <Keyboard
  108. startKey={0}
  109. endKey={127}
  110. keysOn={keyChannels}
  111. height="100%"
  112. keyboardVelocity={0.75}
  113. onChange={Channel.handle({ setKeyChannels, generator: generator.current!, channel, })}
  114. keyboardMapping={keyboardMapping}
  115. midiInput={inputs.length > 0 && typeof input! === 'number' ? inputs[input].input : undefined}
  116. />
  117. </div>
  118. </div>
  119. </React.Fragment>
  120. )
  121. }
  122. const container = window.document.createElement('div')
  123. container.style.display = 'contents'
  124. window.document.body.appendChild(container)
  125. ReactDOM.render(<App />, container)