@tremolo-ui/react 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tremolo-ui/react",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "React component library for audio app",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -42,7 +42,8 @@
42
42
  "react-dom": "^18 || ^19"
43
43
  },
44
44
  "dependencies": {
45
- "@tremolo-ui/functions": "^0.2.1",
45
+ "@tremolo-ui/dom": "^0.3.0",
46
+ "@tremolo-ui/functions": "^0.3.0",
46
47
  "clsx": "^2.1.1",
47
48
  "zustand": "^5.0.3"
48
49
  },
@@ -1,39 +1,32 @@
1
- import { useCallback, useEffect, useState } from 'react'
1
+ import { useEffect, useMemo, useSyncExternalStore } from 'react'
2
2
 
3
- /** @private */
4
- export const PERMISSION_DENIED = 'PERMISSION_DENIED'
5
- /** @private */
6
- export const NOT_SUPPORTED = 'NOT_SUPPORTED'
3
+ import { createMIDIAccess } from '@tremolo-ui/dom'
7
4
 
8
- /** @private */
9
- export type MIDIAccessError = typeof PERMISSION_DENIED | typeof NOT_SUPPORTED
5
+ export {
6
+ NOT_SUPPORTED,
7
+ PERMISSION_DENIED,
8
+ type MIDIAccessError,
9
+ } from '@tremolo-ui/dom'
10
10
 
11
11
  /**
12
12
  * Hooks for requesting MIDI access in the browser. The first argument allows you to choose whether to request access on mount.
13
13
  */
14
14
  export function useMIDIAccess(requestOnMount = true) {
15
- const [midiAccess, setMidiAccess] = useState<MIDIAccess | null>(null)
16
- const [error, setError] = useState<MIDIAccessError | null>(null)
15
+ const instance = useMemo(() => createMIDIAccess(), [])
17
16
 
18
- const request = useCallback(() => {
19
- if (navigator.requestMIDIAccess) {
20
- navigator
21
- .requestMIDIAccess()
22
- .then((access) => {
23
- setMidiAccess(access)
24
- setError(null)
25
- })
26
- .catch(() => {
27
- setError(PERMISSION_DENIED)
28
- })
29
- } else {
30
- setError(NOT_SUPPORTED)
31
- }
32
- }, [])
17
+ const { midiAccess, error } = useSyncExternalStore(
18
+ instance.subscribe,
19
+ instance.getState,
20
+ instance.getServerState,
21
+ )
33
22
 
34
23
  useEffect(() => {
35
- if (requestOnMount) request()
36
- }, [requestOnMount, request])
24
+ if (requestOnMount) instance.request()
25
+ }, [instance, requestOnMount])
37
26
 
38
- return { request, midiAccess, error }
27
+ useEffect(() => {
28
+ return () => instance.destroy()
29
+ }, [instance])
30
+
31
+ return { request: instance.request, midiAccess, error }
39
32
  }
@@ -1,18 +1,6 @@
1
- import { useCallback } from 'react'
1
+ import { useEffect } from 'react'
2
2
 
3
- import { useMIDIMessage } from './useMIDIMessage'
4
-
5
- const MIDI_EVENT_TO_NUMBER = {
6
- NOTE_ON: 0x90,
7
- NOTE_OFF: 0x80,
8
- PITCH_BEND: 0xe0,
9
- }
10
-
11
- // const NUMBER_TO_MIDI_EVENT: Record<number, string> = {
12
- // 0x90: 'NOTE_ON',
13
- // 0x80: 'NOTE_OFF',
14
- // 0xe0: 'PITCH_BEND',
15
- // }
3
+ import { createMIDIInput } from '@tremolo-ui/dom'
16
4
 
17
5
  /**
18
6
  * Hooks for handling note on/off events. To be used with useMIDIAccess. Internally uses useMIDIMessage.
@@ -23,27 +11,12 @@ export function useMIDIInput(
23
11
  onNoteOffEvent?: (note: number) => void,
24
12
  onPitchBendEvent?: (msb: number, lsb: number) => void,
25
13
  ) {
26
- const handleMIDIMessage = useCallback(
27
- (event: MIDIMessageEvent) => {
28
- if (!event.data) return
29
- // event.data[0] ... command
30
- // event.data[1] ... note, MSB (Most Significant Byte)
31
- // event.data[2] ... velocity, LSB (Least Significant Byte)
32
- const kind = event.data[0] & 0xf0
33
-
34
- if (
35
- kind == MIDI_EVENT_TO_NUMBER.NOTE_OFF ||
36
- (kind == MIDI_EVENT_TO_NUMBER.NOTE_ON && event.data[2] == 0)
37
- ) {
38
- onNoteOffEvent?.(event.data[1])
39
- } else if (kind == MIDI_EVENT_TO_NUMBER.NOTE_ON) {
40
- onNoteOnEvent?.(event.data[1], event.data[2])
41
- } else if (kind == MIDI_EVENT_TO_NUMBER.PITCH_BEND) {
42
- onPitchBendEvent?.(event.data[1], event.data[2])
43
- }
44
- },
45
- [onNoteOffEvent, onNoteOnEvent, onPitchBendEvent],
46
- )
47
-
48
- useMIDIMessage(midiAccess, handleMIDIMessage)
14
+ useEffect(() => {
15
+ const instance = createMIDIInput(midiAccess, {
16
+ onNoteOnEvent,
17
+ onNoteOffEvent,
18
+ onPitchBendEvent,
19
+ })
20
+ return () => instance.destroy()
21
+ }, [midiAccess, onNoteOnEvent, onNoteOffEvent, onPitchBendEvent])
49
22
  }
@@ -1,5 +1,7 @@
1
1
  import { useEffect } from 'react'
2
2
 
3
+ import { createMIDIMessage } from '@tremolo-ui/dom'
4
+
3
5
  /**
4
6
  * Hooks for when you want to process MIDI events in more detail than useMIDIInput.
5
7
  */
@@ -8,16 +10,7 @@ export function useMIDIMessage(
8
10
  onMIDIMessage: (event: MIDIMessageEvent) => void,
9
11
  ) {
10
12
  useEffect(() => {
11
- if (!midiAccess) return
12
-
13
- for (const input of midiAccess.inputs.values()) {
14
- input.addEventListener('midimessage', onMIDIMessage)
15
- }
16
-
17
- return () => {
18
- for (const input of midiAccess.inputs.values()) {
19
- input.removeEventListener('midimessage', onMIDIMessage)
20
- }
21
- }
13
+ const instance = createMIDIMessage(midiAccess, onMIDIMessage)
14
+ return () => instance.destroy()
22
15
  }, [midiAccess, onMIDIMessage])
23
16
  }