@tremolo-ui/react 0.1.5 → 0.1.6
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/dist/index.cjs +171 -95
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +79 -32
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +80 -56
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +82 -58
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +160 -91
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/components/Knob/index.css +54 -23
- package/src/components/Knob/index.tsx +30 -25
- package/src/components/NumberInput/index.tsx +3 -4
- package/src/components/Piano/context.tsx +1 -1
- package/src/components/Piano/index.css +24 -8
- package/src/components/Piano/index.tsx +253 -217
- package/src/components/Piano/key.tsx +3 -3
- package/src/components/Slider/Track.tsx +2 -2
- package/src/components/Slider/index.tsx +2 -2
- package/src/components/XYPad/index.tsx +2 -2
- package/src/hooks/useAnimationFrame.ts +3 -0
- package/src/hooks/useCallbackRef.ts +2 -2
- package/src/hooks/useDrag.ts +2 -1
- package/src/hooks/useDragWithElement.ts +2 -1
- package/src/hooks/useEventListener.ts +3 -0
- package/src/hooks/useInterval.ts +3 -0
- package/src/hooks/useLongPress.ts +3 -0
- package/src/hooks/useMIDIAccess.ts +40 -0
- package/src/hooks/useMIDIInput.ts +50 -0
- package/src/hooks/useMIDIMessage.ts +24 -0
- package/src/hooks/useRefCallbackEvent.ts +4 -0
- package/src/index.ts +19 -12
- package/src/styles/global.css +1 -1
|
@@ -5,6 +5,9 @@ import { useCallbackRef } from './useCallbackRef'
|
|
|
5
5
|
type Target = EventTarget | null | (() => EventTarget | null)
|
|
6
6
|
type Options = boolean | AddEventListenerOptions
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* @category hooks
|
|
10
|
+
*/
|
|
8
11
|
export function useEventListener<K extends keyof DocumentEventMap>(
|
|
9
12
|
target: Target,
|
|
10
13
|
event: K,
|
package/src/hooks/useInterval.ts
CHANGED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
/** @private */
|
|
4
|
+
export const PERMISSION_DENIED = 'PERMISSION_DENIED'
|
|
5
|
+
/** @private */
|
|
6
|
+
export const NOT_SUPPORTED = 'NOT_SUPPORTED'
|
|
7
|
+
|
|
8
|
+
/** @private */
|
|
9
|
+
export type MIDIAccessError = typeof PERMISSION_DENIED | typeof NOT_SUPPORTED
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Hooks for requesting MIDI access in the browser. The first argument allows you to choose whether to request access on mount.
|
|
13
|
+
* @category hooks
|
|
14
|
+
*/
|
|
15
|
+
export function useMIDIAccess(requestOnMount = true) {
|
|
16
|
+
const [midiAccess, setMidiAccess] = useState<MIDIAccess | null>(null)
|
|
17
|
+
const [error, setError] = useState<MIDIAccessError | null>(null)
|
|
18
|
+
|
|
19
|
+
const request = useCallback(() => {
|
|
20
|
+
if (navigator.requestMIDIAccess) {
|
|
21
|
+
navigator
|
|
22
|
+
.requestMIDIAccess()
|
|
23
|
+
.then((access) => {
|
|
24
|
+
setMidiAccess(access)
|
|
25
|
+
setError(null)
|
|
26
|
+
})
|
|
27
|
+
.catch(() => {
|
|
28
|
+
setError(PERMISSION_DENIED)
|
|
29
|
+
})
|
|
30
|
+
} else {
|
|
31
|
+
setError(NOT_SUPPORTED)
|
|
32
|
+
}
|
|
33
|
+
}, [])
|
|
34
|
+
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (requestOnMount) request()
|
|
37
|
+
}, [requestOnMount, request])
|
|
38
|
+
|
|
39
|
+
return { request, midiAccess, error }
|
|
40
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { useCallback } from 'react'
|
|
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
|
+
// }
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Hooks for handling note on/off events. To be used with useMIDIAccess. Internally uses useMIDIMessage.
|
|
19
|
+
* @category hooks
|
|
20
|
+
*/
|
|
21
|
+
export function useMIDIInput(
|
|
22
|
+
midiAccess: MIDIAccess | null,
|
|
23
|
+
onNoteOnEvent?: (note: number, velocity: number) => void,
|
|
24
|
+
onNoteOffEvent?: (note: number) => void,
|
|
25
|
+
onPitchBendEvent?: (msb: number, lsb: number) => void,
|
|
26
|
+
) {
|
|
27
|
+
const handleMIDIMessage = useCallback(
|
|
28
|
+
(event: MIDIMessageEvent) => {
|
|
29
|
+
if (!event.data) return
|
|
30
|
+
// event.data[0] ... command
|
|
31
|
+
// event.data[1] ... note, MSB (Most Significant Byte)
|
|
32
|
+
// event.data[2] ... velocity, LSB (Least Significant Byte)
|
|
33
|
+
const kind = event.data[0] & 0xf0
|
|
34
|
+
|
|
35
|
+
if (
|
|
36
|
+
kind == MIDI_EVENT_TO_NUMBER.NOTE_OFF ||
|
|
37
|
+
(kind == MIDI_EVENT_TO_NUMBER.NOTE_ON && event.data[2] == 0)
|
|
38
|
+
) {
|
|
39
|
+
onNoteOffEvent?.(event.data[1])
|
|
40
|
+
} else if (kind == MIDI_EVENT_TO_NUMBER.NOTE_ON) {
|
|
41
|
+
onNoteOnEvent?.(event.data[1], event.data[2])
|
|
42
|
+
} else if (kind == MIDI_EVENT_TO_NUMBER.PITCH_BEND) {
|
|
43
|
+
onPitchBendEvent?.(event.data[1], event.data[2])
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
[onNoteOffEvent, onNoteOnEvent, onPitchBendEvent],
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
useMIDIMessage(midiAccess, handleMIDIMessage)
|
|
50
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { useEffect } from 'react'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Hooks for when you want to process MIDI events in more detail than useMIDIInput.
|
|
5
|
+
* @category hooks
|
|
6
|
+
*/
|
|
7
|
+
export function useMIDIMessage(
|
|
8
|
+
midiAccess: MIDIAccess | null,
|
|
9
|
+
onMIDIMessage: (event: MIDIMessageEvent) => void,
|
|
10
|
+
) {
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
if (!midiAccess) return
|
|
13
|
+
|
|
14
|
+
for (const input of midiAccess.inputs.values()) {
|
|
15
|
+
input.addEventListener('midimessage', onMIDIMessage)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return () => {
|
|
19
|
+
for (const input of midiAccess.inputs.values()) {
|
|
20
|
+
input.removeEventListener('midimessage', onMIDIMessage)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}, [midiAccess, onMIDIMessage])
|
|
24
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -34,17 +34,18 @@ export {
|
|
|
34
34
|
type StepperProps,
|
|
35
35
|
} from './components/NumberInput'
|
|
36
36
|
export {
|
|
37
|
+
Piano,
|
|
38
|
+
type PianoProps,
|
|
39
|
+
type PianoMethods,
|
|
40
|
+
getNoteRangeArray,
|
|
41
|
+
WhiteKey,
|
|
37
42
|
BlackKey,
|
|
43
|
+
type KeyProps,
|
|
44
|
+
type KeyMethods,
|
|
38
45
|
KeyLabel,
|
|
39
46
|
type KeyLabelProps,
|
|
40
|
-
type KeyMethods,
|
|
41
|
-
type KeyProps,
|
|
42
|
-
type KeyboardShortcuts,
|
|
43
|
-
Piano,
|
|
44
|
-
type PianoProps,
|
|
45
47
|
SHORTCUTS,
|
|
46
|
-
|
|
47
|
-
getNoteRangeArray,
|
|
48
|
+
type KeyboardShortcuts,
|
|
48
49
|
} from './components/Piano'
|
|
49
50
|
export {
|
|
50
51
|
Scale,
|
|
@@ -77,12 +78,18 @@ export {
|
|
|
77
78
|
type XYPadThumbMethods,
|
|
78
79
|
} from './components/XYPad'
|
|
79
80
|
|
|
81
|
+
// hooks
|
|
80
82
|
export { useAnimationFrame } from './hooks/useAnimationFrame'
|
|
81
|
-
export { useDrag
|
|
82
|
-
export {
|
|
83
|
-
useDragWithElement,
|
|
84
|
-
type UseDragWithElementProps,
|
|
85
|
-
} from './hooks/useDragWithElement'
|
|
83
|
+
export { useDrag } from './hooks/useDrag'
|
|
84
|
+
export { useDragWithElement } from './hooks/useDragWithElement'
|
|
86
85
|
export { useEventListener } from './hooks/useEventListener'
|
|
87
86
|
export { useInterval } from './hooks/useInterval'
|
|
88
87
|
export { useLongPress } from './hooks/useLongPress'
|
|
88
|
+
export {
|
|
89
|
+
useMIDIAccess,
|
|
90
|
+
NOT_SUPPORTED,
|
|
91
|
+
PERMISSION_DENIED,
|
|
92
|
+
type MIDIAccessError,
|
|
93
|
+
} from './hooks/useMIDIAccess'
|
|
94
|
+
export { useMIDIInput } from './hooks/useMIDIInput'
|
|
95
|
+
export { useMIDIMessage } from './hooks/useMIDIMessage'
|
package/src/styles/global.css
CHANGED