Simple monitor for displaying MIDI status for digital pianos.
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

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. }