Simple monitor for displaying MIDI status for digital pianos.
Não pode escolher mais do que 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

midi.ts 4.8 KiB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. import * as React from 'react'
  2. import { messages } from '../utils/midi'
  3. export type DeviceChannelActive = [
  4. boolean,
  5. boolean,
  6. boolean,
  7. boolean,
  8. boolean,
  9. boolean,
  10. boolean,
  11. boolean,
  12. boolean,
  13. boolean,
  14. boolean,
  15. boolean,
  16. boolean,
  17. boolean,
  18. boolean,
  19. boolean
  20. ]
  21. interface MidiMessageEvent extends Event {
  22. data: [number, number, number]
  23. }
  24. interface UseMidiReturn {
  25. midiAccess?: MIDIAccess
  26. lastStateChangeTimestamp?: number
  27. }
  28. interface ChannelData {
  29. channel: number
  30. key: number
  31. velocity: number
  32. }
  33. export const useMidi = (): UseMidiReturn => {
  34. const [lastStateChangeTimestamp, setLastStateChangeTimestamp] = React.useState<number>()
  35. const [midiAccess, setMidiAccess] = React.useState<MIDIAccess>()
  36. React.useEffect(() => {
  37. const stateChangeListener = (e: Event): void => {
  38. setLastStateChangeTimestamp(e.timeStamp)
  39. }
  40. window.navigator.requestMIDIAccess().then((midiAccess) => {
  41. setMidiAccess(midiAccess)
  42. setLastStateChangeTimestamp(Date.now())
  43. midiAccess.addEventListener('statechange', stateChangeListener)
  44. })
  45. return (): void => {
  46. midiAccess?.removeEventListener('statechange', stateChangeListener)
  47. }
  48. }, [])
  49. return {
  50. midiAccess,
  51. lastStateChangeTimestamp
  52. }
  53. }
  54. interface UseMidiActivityReturn {
  55. isChannelActive: DeviceChannelActive
  56. unaCorda: number
  57. sostenuto: number
  58. sustain: number
  59. keyChannels: ChannelData[]
  60. }
  61. export const useMidiActivity = (currentDevice?: MIDIInput): UseMidiActivityReturn => {
  62. const [isChannelActive, setIsChannelActive] = React.useState<DeviceChannelActive>([
  63. false,
  64. false,
  65. false,
  66. false,
  67. false,
  68. false,
  69. false,
  70. false,
  71. false,
  72. false,
  73. false,
  74. false,
  75. false,
  76. false,
  77. false,
  78. false
  79. ])
  80. const currentDeviceActiveTimeoutRef = React.useRef<number>()
  81. const [unaCorda, setUnaCorda] = React.useState(0)
  82. const [sostenuto, setSostenuto] = React.useState(0)
  83. const [sustain, setSustain] = React.useState(0)
  84. const [keyChannels, setKeyChannels] = React.useState<ChannelData[]>([])
  85. React.useEffect(() => {
  86. if (typeof currentDevice === 'undefined') {
  87. return
  88. }
  89. const addActivity = (channel: number): void => {
  90. setIsChannelActive(
  91. (oldCurrentDeviceActive) =>
  92. oldCurrentDeviceActive.map((state, i) =>
  93. i === channel ? true : state
  94. ) as DeviceChannelActive
  95. )
  96. window.clearTimeout(currentDeviceActiveTimeoutRef.current)
  97. currentDeviceActiveTimeoutRef.current = window.setTimeout(() => {
  98. setIsChannelActive(
  99. (oldCurrentDeviceActive) =>
  100. oldCurrentDeviceActive.map((state, i) =>
  101. i === channel ? false : state
  102. ) as DeviceChannelActive
  103. )
  104. }, 100)
  105. }
  106. const listener = (e: Event): void => {
  107. if (e.type !== 'midimessage') {
  108. return
  109. }
  110. const midiEvent = e as MidiMessageEvent
  111. const [messageType, param1, param2] = midiEvent.data
  112. const channel = messageType & messages.CHANNEL_BITMASK
  113. addActivity(channel)
  114. if (channel === messages.CHANNEL_INDEX_PERCUSSION) {
  115. return
  116. }
  117. switch (messageType & messages.TYPE_BITMASK) {
  118. case messages.types.CONTINUOUS_CONTROL: {
  119. const controlNumber = param1
  120. const value = param2
  121. switch (controlNumber) {
  122. case messages.params.continuousControl.sustain:
  123. setSustain(value)
  124. return
  125. case messages.params.continuousControl.sostenuto:
  126. setSostenuto(value)
  127. return
  128. case messages.params.continuousControl.unaCorda:
  129. setUnaCorda(value)
  130. return
  131. default:
  132. break
  133. }
  134. return
  135. }
  136. case messages.types.NOTE_ON: {
  137. const keyNumber = param1
  138. const velocity = param2
  139. setKeyChannels((oldKeyChannels) => {
  140. if (velocity > 0) {
  141. return [
  142. ...oldKeyChannels,
  143. {
  144. channel,
  145. key: keyNumber,
  146. velocity
  147. }
  148. ]
  149. }
  150. return oldKeyChannels.filter((c) => !(c.channel === channel && c.key === keyNumber))
  151. })
  152. return
  153. }
  154. case messages.types.NOTE_OFF: {
  155. const keyNumber = param1
  156. setKeyChannels((oldKeyChannels) => {
  157. return oldKeyChannels.filter((c) => !(c.channel === channel && c.key === keyNumber))
  158. })
  159. return
  160. }
  161. default:
  162. break
  163. }
  164. }
  165. currentDevice.addEventListener('midimessage', listener)
  166. return () => {
  167. currentDevice.removeEventListener('midimessage', listener)
  168. }
  169. }, [currentDevice])
  170. return {
  171. isChannelActive,
  172. unaCorda,
  173. sostenuto,
  174. sustain,
  175. keyChannels
  176. }
  177. }