zds-pickers 4.0.3 → 4.0.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/.editorconfig +19 -0
- package/.github/workflows/codeql-analysis.yml +67 -0
- package/.nvmrc +1 -0
- package/.prettierignore +0 -0
- package/.prettierrc +13 -0
- package/.storybook/main.ts +42 -0
- package/.storybook/manager.ts +12 -0
- package/.storybook/preview.tsx +26 -0
- package/.vscode/extensions.json +32 -0
- package/.vscode/javascript.code-snippets +229 -0
- package/.vscode/settings.json +358 -0
- package/biome.jsonc +158 -0
- package/dist/index.cjs.js +15 -32
- package/dist/index.es.js +4144 -10129
- package/dist/types/hooks/useComponentSize.d.ts +5 -0
- package/dist/types/hooks/useStateWithDynamicDefault.d.ts +2 -0
- package/dist/types/index.d.ts +266 -2
- package/dist/types/main.d.ts +19 -0
- package/dist/types/midi/ccValues.d.ts +3 -0
- package/dist/types/midi/export.d.ts +22 -0
- package/dist/types/other/DefaultTooltip.d.ts +10 -0
- package/dist/types/other/OctavePlayer.d.ts +20 -0
- package/dist/types/other/SVGText.d.ts +17 -0
- package/dist/types/other/SoundFontProvider.d.ts +17 -0
- package/dist/types/pickers/CCPicker.d.ts +7 -0
- package/dist/types/pickers/ChannelMappingPicker.d.ts +14 -0
- package/dist/types/pickers/ChannelPicker.d.ts +8 -0
- package/dist/types/pickers/Knob.d.ts +12 -0
- package/dist/types/pickers/KnobPicker.d.ts +27 -0
- package/dist/types/pickers/LatchPicker.d.ts +8 -0
- package/dist/types/pickers/MappingPicker.d.ts +11 -0
- package/dist/types/pickers/NotePicker.d.ts +20 -0
- package/dist/types/pickers/PianoPicker.d.ts +9 -0
- package/dist/types/pickers/PolarityPicker.d.ts +14 -0
- package/dist/types/pickers/ResponseCurve.d.ts +40 -0
- package/dist/types/pickers/ResponseCurvePicker.d.ts +15 -0
- package/dist/types/pickers/Select.d.ts +29 -0
- package/dist/types/pickers/StatusPicker.d.ts +12 -0
- package/dist/types/pickers/ValuePicker.d.ts +15 -0
- package/dist/types/pickers/knobSkin10.d.ts +14 -0
- package/dist/types/rotaryKnob/InternalInput.d.ts +18 -0
- package/dist/types/rotaryKnob/Types.d.ts +5 -0
- package/dist/types/rotaryKnob/helpers/DrawCircle.d.ts +18 -0
- package/dist/types/rotaryKnob/helpers/DrawLine.d.ts +15 -0
- package/dist/types/rotaryKnob/helpers/HelpersOverlay.d.ts +11 -0
- package/dist/types/rotaryKnob/helpers/KnobVisualHelpers.d.ts +30 -0
- package/dist/types/rotaryKnob/index.d.ts +49 -0
- package/dist/types/rotaryKnob/knobdefaultskin.d.ts +6 -0
- package/dist/types/rotaryKnob/utils.d.ts +49 -0
- package/dist/types/utils.d.ts +9 -0
- package/lib/@types/nebiru-react-rotary-knob.d.ts +20 -0
- package/lib/@types/react-svgmt.d.ts +74 -0
- package/lib/@types/zds-mappings.d.ts +55 -0
- package/lib/@types/zds-react-piano.d.ts +17 -0
- package/lib/hooks/useComponentSize.ts +52 -0
- package/lib/hooks/useStateWithDynamicDefault.ts +13 -0
- package/lib/main.ts +34 -0
- package/lib/midi/ccValues.ts +139 -0
- package/lib/midi/export.ts +54 -0
- package/lib/other/DefaultTooltip.ts +14 -0
- package/lib/other/OctavePlayer.tsx +141 -0
- package/lib/other/SVGText.tsx +221 -0
- package/lib/other/SoundFontProvider.tsx +130 -0
- package/lib/pickers/CCPicker.tsx +28 -0
- package/lib/pickers/ChannelMappingPicker.tsx +135 -0
- package/lib/pickers/ChannelPicker.tsx +76 -0
- package/lib/pickers/Knob.tsx +93 -0
- package/lib/pickers/KnobPicker.tsx +93 -0
- package/lib/pickers/LatchPicker.tsx +33 -0
- package/lib/pickers/MappingPicker.tsx +68 -0
- package/lib/pickers/NotePicker.tsx +95 -0
- package/lib/pickers/PianoPicker.tsx +77 -0
- package/lib/pickers/PolarityPicker.tsx +39 -0
- package/lib/pickers/ResponseCurve.tsx +304 -0
- package/lib/pickers/ResponseCurvePicker.tsx +74 -0
- package/lib/pickers/Select.tsx +248 -0
- package/lib/pickers/StatusPicker.tsx +28 -0
- package/lib/pickers/ValuePicker.tsx +67 -0
- package/lib/pickers/knobSkin10.tsx +94 -0
- package/lib/rotaryKnob/InternalInput.tsx +43 -0
- package/lib/rotaryKnob/Types.ts +6 -0
- package/lib/rotaryKnob/helpers/DrawCircle.tsx +41 -0
- package/lib/rotaryKnob/helpers/DrawLine.tsx +32 -0
- package/lib/rotaryKnob/helpers/HelpersOverlay.tsx +43 -0
- package/lib/rotaryKnob/helpers/KnobVisualHelpers.tsx +138 -0
- package/lib/rotaryKnob/index.tsx +417 -0
- package/lib/rotaryKnob/knobdefaultskin.ts +56 -0
- package/lib/rotaryKnob/utils.ts +112 -0
- package/lib/temp/acoustic_grand_piano-mp3.js +94 -0
- package/lib/utils.ts +41 -0
- package/package.json +3 -7
- package/stories/assets/sampleStyles.css +349 -0
- package/stories/assets/utils.js +7 -0
- package/stories/picker.stories.tsx +313 -0
- package/tsconfig.app.json +26 -0
- package/tsconfig.json +34 -0
- package/tsconfig.node.json +24 -0
- package/vite.config.ts +45 -0
- package/dist/index.cjs.js.map +0 -1
- package/dist/index.d.ts +0 -244
- package/dist/index.es.js.map +0 -1
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import cl from 'classnames'
|
|
2
|
+
import { forwardRef, useCallback, useEffect, useState } from 'react'
|
|
3
|
+
import type { GroupBase, SelectInstance } from 'react-select'
|
|
4
|
+
import { arraySequence } from '../utils'
|
|
5
|
+
import Select from './Select'
|
|
6
|
+
import type { Option, SelectProps } from './Select'
|
|
7
|
+
|
|
8
|
+
type InternalValue = number | 'separator'
|
|
9
|
+
// type ExternalValue = number
|
|
10
|
+
|
|
11
|
+
type ChannelMappingOption = Option<InternalValue>
|
|
12
|
+
|
|
13
|
+
const defaultOptions: ChannelMappingOption[] = arraySequence(16).map(value => ({
|
|
14
|
+
value,
|
|
15
|
+
label: value,
|
|
16
|
+
}))
|
|
17
|
+
|
|
18
|
+
interface ChannelMappingPickerProps extends SelectProps<InternalValue> {
|
|
19
|
+
channels: Option<number>[]
|
|
20
|
+
className?: string
|
|
21
|
+
filterOptions?: (
|
|
22
|
+
candidate: Option<InternalValue>,
|
|
23
|
+
inputValue: string,
|
|
24
|
+
) => boolean
|
|
25
|
+
value: number
|
|
26
|
+
onChange: (value: InternalValue) => void
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
type ChannelMappingPickerRef = SelectInstance<
|
|
30
|
+
Option<number>,
|
|
31
|
+
false,
|
|
32
|
+
GroupBase<Option<number>>
|
|
33
|
+
>
|
|
34
|
+
|
|
35
|
+
type FormatOptionLabelFunction = NonNullable<
|
|
36
|
+
SelectProps<InternalValue>['formatOptionLabel']
|
|
37
|
+
>
|
|
38
|
+
|
|
39
|
+
const ChannelMappingPicker = forwardRef<
|
|
40
|
+
SelectInstance<
|
|
41
|
+
Option<InternalValue>,
|
|
42
|
+
false,
|
|
43
|
+
GroupBase<Option<InternalValue>>
|
|
44
|
+
>,
|
|
45
|
+
ChannelMappingPickerProps
|
|
46
|
+
>(({ channels, className, ...rest }, ref) => {
|
|
47
|
+
const [options, setOptions] = useState<Option<InternalValue>[]>([])
|
|
48
|
+
|
|
49
|
+
const formatOptionLabel: FormatOptionLabelFunction = useCallback(
|
|
50
|
+
({ value, label }, { context }) => {
|
|
51
|
+
if (value === 'separator') {
|
|
52
|
+
return <span className="zds-mappings-user-mapping">{label}</span>
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (typeof value !== 'number') return null
|
|
56
|
+
|
|
57
|
+
const mapping = channels[value]
|
|
58
|
+
|
|
59
|
+
return mapping ? (
|
|
60
|
+
<span className="mapping-entry">
|
|
61
|
+
<b>
|
|
62
|
+
{value + 1}
|
|
63
|
+
{context === 'value' && ' -'}
|
|
64
|
+
</b>
|
|
65
|
+
<span className="mapping-entry-label">{mapping.label}</span>
|
|
66
|
+
</span>
|
|
67
|
+
) : (
|
|
68
|
+
<span className="mapping-entry empty-mapping-entry">
|
|
69
|
+
<b>
|
|
70
|
+
{value + 1}
|
|
71
|
+
{context === 'value' && ' -'}
|
|
72
|
+
</b>
|
|
73
|
+
<span className="mapping-entry-label">no mapping</span>
|
|
74
|
+
</span>
|
|
75
|
+
)
|
|
76
|
+
},
|
|
77
|
+
[channels],
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
const result = defaultOptions.sort(
|
|
82
|
+
(a: ChannelMappingOption, b: ChannelMappingOption) => {
|
|
83
|
+
if (
|
|
84
|
+
typeof a.value === 'number' &&
|
|
85
|
+
typeof b.value === 'number' &&
|
|
86
|
+
channels[a.value] &&
|
|
87
|
+
!channels[b.value]
|
|
88
|
+
) {
|
|
89
|
+
return -1
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (
|
|
93
|
+
typeof a.value === 'number' &&
|
|
94
|
+
typeof b.value === 'number' &&
|
|
95
|
+
!channels[a.value] &&
|
|
96
|
+
channels[b.value]
|
|
97
|
+
) {
|
|
98
|
+
return 1
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (typeof a.value === 'number' && typeof b.value === 'number') {
|
|
102
|
+
return a.value - b.value
|
|
103
|
+
}
|
|
104
|
+
return 0
|
|
105
|
+
},
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
const numFilled = result.filter(
|
|
109
|
+
({ value }) => typeof value === 'number' && !!channels[value],
|
|
110
|
+
).length
|
|
111
|
+
|
|
112
|
+
const separatorExists = result.some(({ value }) => value === 'separator')
|
|
113
|
+
|
|
114
|
+
if (numFilled && numFilled < 16 && !separatorExists) {
|
|
115
|
+
// insert a separator after the count of filled items
|
|
116
|
+
result.splice(numFilled, 0, { label: <hr />, value: 'separator' })
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
setOptions(result)
|
|
120
|
+
}, [channels])
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
<Select<InternalValue>
|
|
124
|
+
{...rest}
|
|
125
|
+
className={cl(className, 'channel-mapping-picker')}
|
|
126
|
+
formatOptionLabel={formatOptionLabel}
|
|
127
|
+
options={options}
|
|
128
|
+
ref={ref}
|
|
129
|
+
/>
|
|
130
|
+
)
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
export default ChannelMappingPicker
|
|
134
|
+
|
|
135
|
+
export type { ChannelMappingPickerProps, ChannelMappingPickerRef }
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import cl from 'classnames'
|
|
2
|
+
import { forwardRef } from 'react'
|
|
3
|
+
import type { GroupBase, SelectInstance } from 'react-select'
|
|
4
|
+
import { arraySequence } from '../utils'
|
|
5
|
+
import Select from './Select'
|
|
6
|
+
import type { Option, SelectProps } from './Select'
|
|
7
|
+
|
|
8
|
+
type Channel =
|
|
9
|
+
| 0
|
|
10
|
+
| 1
|
|
11
|
+
| 2
|
|
12
|
+
| 3
|
|
13
|
+
| 4
|
|
14
|
+
| 5
|
|
15
|
+
| 6
|
|
16
|
+
| 7
|
|
17
|
+
| 8
|
|
18
|
+
| 9
|
|
19
|
+
| 10
|
|
20
|
+
| 11
|
|
21
|
+
| 12
|
|
22
|
+
| 13
|
|
23
|
+
| 14
|
|
24
|
+
| 15
|
|
25
|
+
|
|
26
|
+
interface FormatOptionLabelContext {
|
|
27
|
+
context: 'menu' | 'value'
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
type ChannelPickerProps = SelectProps<Channel>
|
|
31
|
+
|
|
32
|
+
type ChannelPickerRef = SelectInstance<
|
|
33
|
+
Option<Channel>,
|
|
34
|
+
false,
|
|
35
|
+
GroupBase<Option<Channel>>
|
|
36
|
+
>
|
|
37
|
+
|
|
38
|
+
const options: Option<Channel>[] = arraySequence(16).map(value => ({
|
|
39
|
+
value: value as Channel,
|
|
40
|
+
label: `Channel ${value + 1}`,
|
|
41
|
+
}))
|
|
42
|
+
|
|
43
|
+
const formatOptionLabel = (
|
|
44
|
+
option: Option<unknown>,
|
|
45
|
+
{ context }: FormatOptionLabelContext,
|
|
46
|
+
) =>
|
|
47
|
+
context === 'value' ? option.label : Number.parseInt(String(option.value)) + 1 // menu, e.g. the list of options
|
|
48
|
+
|
|
49
|
+
const filterOptions = (option: Option<Channel>, input: string) => {
|
|
50
|
+
// console.log(option, input)
|
|
51
|
+
|
|
52
|
+
if (input) {
|
|
53
|
+
return String(option.label).toLowerCase().includes(input.toLowerCase())
|
|
54
|
+
}
|
|
55
|
+
return true
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const ChannelPicker = forwardRef<
|
|
59
|
+
SelectInstance<Option<Channel>, false, GroupBase<Option<Channel>>>,
|
|
60
|
+
ChannelPickerProps
|
|
61
|
+
>(({ className, ...rest }, ref) => {
|
|
62
|
+
return (
|
|
63
|
+
<Select
|
|
64
|
+
{...rest}
|
|
65
|
+
className={cl(className, 'channel-picker')}
|
|
66
|
+
filterOptions={filterOptions}
|
|
67
|
+
formatOptionLabel={formatOptionLabel}
|
|
68
|
+
options={options}
|
|
69
|
+
ref={ref}
|
|
70
|
+
/>
|
|
71
|
+
)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
export default ChannelPicker
|
|
75
|
+
|
|
76
|
+
export type { Channel, ChannelPickerProps, ChannelPickerRef }
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { useCallback } from 'react'
|
|
2
|
+
import useStateWithDynamicDefault from '../hooks/useStateWithDynamicDefault'
|
|
3
|
+
import RotaryKnob from '../rotaryKnob'
|
|
4
|
+
import { assertRange } from '../utils'
|
|
5
|
+
import knobSkin10 from './knobSkin10'
|
|
6
|
+
|
|
7
|
+
type KnobProps = {
|
|
8
|
+
disabled?: boolean
|
|
9
|
+
max?: number
|
|
10
|
+
min?: number
|
|
11
|
+
onChange?: (value: number) => void
|
|
12
|
+
value?: number
|
|
13
|
+
wheelEnabled?: boolean
|
|
14
|
+
wheelSensitivity?: number
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const Knob = (props: KnobProps) => {
|
|
18
|
+
const {
|
|
19
|
+
disabled = false,
|
|
20
|
+
max = 127,
|
|
21
|
+
min = 0,
|
|
22
|
+
onChange,
|
|
23
|
+
value: initialValue = 0,
|
|
24
|
+
wheelEnabled = false,
|
|
25
|
+
wheelSensitivity = 0.1,
|
|
26
|
+
...rest
|
|
27
|
+
} = props
|
|
28
|
+
|
|
29
|
+
const [value, setValue] = useStateWithDynamicDefault(initialValue)
|
|
30
|
+
|
|
31
|
+
const handleChange = (val: number) => {
|
|
32
|
+
if (disabled) return
|
|
33
|
+
|
|
34
|
+
const newValue = assertRange(Number.parseInt(String(val), 10), max, min)
|
|
35
|
+
setValue(newValue)
|
|
36
|
+
if (onChange) {
|
|
37
|
+
onChange(newValue)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const handleWheel = useCallback(
|
|
42
|
+
(event: React.WheelEvent<HTMLDivElement>) => {
|
|
43
|
+
if (!wheelEnabled || disabled) return
|
|
44
|
+
|
|
45
|
+
event.preventDefault()
|
|
46
|
+
const delta = -event.deltaY * wheelSensitivity
|
|
47
|
+
const newValue = assertRange(value + delta, max, min)
|
|
48
|
+
|
|
49
|
+
setValue(newValue)
|
|
50
|
+
if (onChange) {
|
|
51
|
+
onChange(newValue)
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
[
|
|
55
|
+
wheelEnabled,
|
|
56
|
+
disabled,
|
|
57
|
+
wheelSensitivity,
|
|
58
|
+
value,
|
|
59
|
+
max,
|
|
60
|
+
min,
|
|
61
|
+
onChange,
|
|
62
|
+
setValue,
|
|
63
|
+
],
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<RotaryKnob
|
|
68
|
+
clampMax={320}
|
|
69
|
+
clampMin={40}
|
|
70
|
+
className="zds-pickers__knob-container"
|
|
71
|
+
disabled={disabled}
|
|
72
|
+
onChange={handleChange}
|
|
73
|
+
onWheel={wheelEnabled ? handleWheel : undefined}
|
|
74
|
+
preciseMode={false}
|
|
75
|
+
rotateDegrees={180}
|
|
76
|
+
/**
|
|
77
|
+
* To see all skins:
|
|
78
|
+
* http://react-rotary-knob-skins-preview.surge.sh/
|
|
79
|
+
*
|
|
80
|
+
* NOTE: we've copied the one we use into this package because the skins
|
|
81
|
+
* pack package does not work in Electron.
|
|
82
|
+
*/
|
|
83
|
+
skin={knobSkin10}
|
|
84
|
+
style={{ opacity: disabled ? 0.4 : 1 }}
|
|
85
|
+
unlockDistance={0}
|
|
86
|
+
{...{ defaultValue: value, max, min, value, ...rest }}
|
|
87
|
+
/>
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export default Knob
|
|
92
|
+
|
|
93
|
+
export type { KnobProps }
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { forwardRef, useMemo } from 'react'
|
|
2
|
+
import type { GroupBase, SelectInstance } from 'react-select'
|
|
3
|
+
import { arraySequence } from '../utils'
|
|
4
|
+
import Knob from './Knob'
|
|
5
|
+
import Select from './Select'
|
|
6
|
+
import type { Option, SelectProps } from './Select'
|
|
7
|
+
|
|
8
|
+
type KnobPickerProps = SelectProps<number> & {
|
|
9
|
+
highToLow?: boolean
|
|
10
|
+
includeLabel?: boolean
|
|
11
|
+
includePicker?: boolean
|
|
12
|
+
knobProps?: Partial<KnobPickerProps>
|
|
13
|
+
max?: number
|
|
14
|
+
min?: number
|
|
15
|
+
wheelEnabled?: boolean
|
|
16
|
+
wheelSensitivity?: number
|
|
17
|
+
value: number
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type KnobPickerRef = SelectInstance<
|
|
21
|
+
Option<number>,
|
|
22
|
+
false,
|
|
23
|
+
GroupBase<Option<number>>
|
|
24
|
+
>
|
|
25
|
+
|
|
26
|
+
const KnobPicker = forwardRef<
|
|
27
|
+
SelectInstance<Option<number>, false, GroupBase<Option<number>>>,
|
|
28
|
+
KnobPickerProps
|
|
29
|
+
>((props, ref) => {
|
|
30
|
+
const {
|
|
31
|
+
disabled,
|
|
32
|
+
highToLow,
|
|
33
|
+
includeLabel,
|
|
34
|
+
includePicker,
|
|
35
|
+
knobProps,
|
|
36
|
+
label,
|
|
37
|
+
max = 127,
|
|
38
|
+
min = 0,
|
|
39
|
+
onChange,
|
|
40
|
+
shrinkLabel,
|
|
41
|
+
value = 0,
|
|
42
|
+
wheelEnabled,
|
|
43
|
+
wheelSensitivity = 0.1,
|
|
44
|
+
...rest
|
|
45
|
+
} = props
|
|
46
|
+
|
|
47
|
+
const options = useMemo(() => {
|
|
48
|
+
const result = arraySequence(max - min + 1)
|
|
49
|
+
.map(i => min + i)
|
|
50
|
+
.map((i): Option<number> => ({ value: i, label: String(i) }))
|
|
51
|
+
return highToLow ? result.reverse() : result
|
|
52
|
+
}, [highToLow, max, min])
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<div className="zds-pickers__container">
|
|
56
|
+
{Boolean(!shrinkLabel && Boolean(label)) && (
|
|
57
|
+
<span className="zds-pickers__label">{label}</span>
|
|
58
|
+
)}
|
|
59
|
+
<div style={{ display: 'inline-flex', gap: 10, alignItems: 'center' }}>
|
|
60
|
+
{Boolean(includePicker) && (
|
|
61
|
+
<Select
|
|
62
|
+
{...{
|
|
63
|
+
...rest,
|
|
64
|
+
disabled,
|
|
65
|
+
isDisabled: disabled,
|
|
66
|
+
onChange,
|
|
67
|
+
options,
|
|
68
|
+
ref,
|
|
69
|
+
value,
|
|
70
|
+
}}
|
|
71
|
+
/>
|
|
72
|
+
)}
|
|
73
|
+
|
|
74
|
+
<Knob
|
|
75
|
+
{...{
|
|
76
|
+
disabled,
|
|
77
|
+
max,
|
|
78
|
+
min,
|
|
79
|
+
onChange,
|
|
80
|
+
value,
|
|
81
|
+
wheelEnabled,
|
|
82
|
+
wheelSensitivity,
|
|
83
|
+
...knobProps,
|
|
84
|
+
}}
|
|
85
|
+
/>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
export default KnobPicker
|
|
92
|
+
|
|
93
|
+
export type { KnobPickerProps, KnobPickerRef }
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { forwardRef } from 'react'
|
|
2
|
+
import type { GroupBase, SelectInstance } from 'react-select'
|
|
3
|
+
import type { Polarity } from './PolarityPicker'
|
|
4
|
+
import Select from './Select'
|
|
5
|
+
import type { Option, SelectProps } from './Select'
|
|
6
|
+
|
|
7
|
+
const options: Option<Polarity>[] = [
|
|
8
|
+
{ value: 0, label: 'Momentary' },
|
|
9
|
+
{ value: 1, label: 'Latching' },
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
type LatchPickerProps = SelectProps<Polarity>
|
|
13
|
+
|
|
14
|
+
type LatchPickerRef = SelectInstance<
|
|
15
|
+
Option<Polarity>,
|
|
16
|
+
false,
|
|
17
|
+
GroupBase<Option<Polarity>>
|
|
18
|
+
>
|
|
19
|
+
|
|
20
|
+
const LatchPicker = forwardRef<
|
|
21
|
+
SelectInstance<Option<Polarity>, false, GroupBase<Option<Polarity>>>,
|
|
22
|
+
LatchPickerProps
|
|
23
|
+
>((props, ref) => (
|
|
24
|
+
<Select
|
|
25
|
+
{...props}
|
|
26
|
+
options={options}
|
|
27
|
+
ref={ref}
|
|
28
|
+
/>
|
|
29
|
+
))
|
|
30
|
+
|
|
31
|
+
export default LatchPicker
|
|
32
|
+
|
|
33
|
+
export type { LatchPickerProps, LatchPickerRef }
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { forwardRef, useMemo } from 'react'
|
|
2
|
+
import type { GroupBase, SelectInstance } from 'react-select'
|
|
3
|
+
import { getStockNames, getUserMappingNames } from 'zds-mappings'
|
|
4
|
+
import Select from './Select'
|
|
5
|
+
import type { Option, SelectProps } from './Select'
|
|
6
|
+
|
|
7
|
+
type MappingPickerProps = SelectProps<string> & {
|
|
8
|
+
allowClearing: boolean
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
type MappingPickerRef = SelectInstance<
|
|
12
|
+
Option<string>,
|
|
13
|
+
false,
|
|
14
|
+
GroupBase<Option<string>>
|
|
15
|
+
>
|
|
16
|
+
|
|
17
|
+
const MappingPicker = forwardRef<
|
|
18
|
+
SelectInstance<Option<string>, false, GroupBase<Option<string>>>,
|
|
19
|
+
MappingPickerProps
|
|
20
|
+
>(({ allowClearing, ...rest }, ref) => {
|
|
21
|
+
const stockMappings = getStockNames()
|
|
22
|
+
const userMappings = getUserMappingNames()
|
|
23
|
+
|
|
24
|
+
const options = useMemo(() => {
|
|
25
|
+
const result = allowClearing
|
|
26
|
+
? [
|
|
27
|
+
{
|
|
28
|
+
value: 'No Mapping',
|
|
29
|
+
label: (
|
|
30
|
+
<span className="zds-mappings-clear-mapping">No Mapping</span>
|
|
31
|
+
),
|
|
32
|
+
},
|
|
33
|
+
]
|
|
34
|
+
: []
|
|
35
|
+
|
|
36
|
+
return [
|
|
37
|
+
...result,
|
|
38
|
+
|
|
39
|
+
{
|
|
40
|
+
label: allowClearing ? <hr /> : '',
|
|
41
|
+
options: userMappings.map(label => ({
|
|
42
|
+
value: label,
|
|
43
|
+
label: <span className="zds-mappings-user-mapping">{label}</span>,
|
|
44
|
+
})),
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
{
|
|
48
|
+
label: userMappings.length ? <hr /> : allowClearing ? <hr /> : '',
|
|
49
|
+
options: stockMappings.map(label => ({
|
|
50
|
+
label,
|
|
51
|
+
value: label,
|
|
52
|
+
})),
|
|
53
|
+
},
|
|
54
|
+
] as Option<string>[]
|
|
55
|
+
}, [allowClearing, stockMappings, userMappings])
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<Select
|
|
59
|
+
{...rest}
|
|
60
|
+
options={options}
|
|
61
|
+
ref={ref}
|
|
62
|
+
/>
|
|
63
|
+
)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
export default MappingPicker
|
|
67
|
+
|
|
68
|
+
export type { MappingPickerProps, MappingPickerRef }
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { forwardRef, useCallback, useMemo } from 'react'
|
|
2
|
+
import type { GroupBase, SelectInstance } from 'react-select'
|
|
3
|
+
import { Midi } from 'tonal'
|
|
4
|
+
import { type MapItem, type Mapping, emptyMapping } from 'zds-mappings'
|
|
5
|
+
import useStateWithDynamicDefault from '../hooks/useStateWithDynamicDefault'
|
|
6
|
+
import { assertRange } from '../utils'
|
|
7
|
+
import Select from './Select'
|
|
8
|
+
import type { Option, SelectProps } from './Select'
|
|
9
|
+
|
|
10
|
+
const { midiToNoteName } = Midi
|
|
11
|
+
|
|
12
|
+
const formattedMapEntry = ({ note, name }: MapItem) =>
|
|
13
|
+
`${note} ${name.length ? '-' : ''} ${name}`
|
|
14
|
+
const formattedListEntry = (label: string, idx: number) => ({
|
|
15
|
+
label,
|
|
16
|
+
value: idx + 1,
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
type NotePickerProps = SelectProps<number> & {
|
|
20
|
+
channel: number
|
|
21
|
+
disabled?: boolean
|
|
22
|
+
isMelodicMode?: boolean
|
|
23
|
+
mapping?: Mapping
|
|
24
|
+
onChange: (value: number) => void
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
type NotePickerRef = SelectInstance<
|
|
28
|
+
Option<number>,
|
|
29
|
+
false,
|
|
30
|
+
GroupBase<Option<number>>
|
|
31
|
+
>
|
|
32
|
+
|
|
33
|
+
const NotePicker = forwardRef<
|
|
34
|
+
SelectInstance<Option<number>, false, GroupBase<Option<number>>>,
|
|
35
|
+
NotePickerProps
|
|
36
|
+
>((props, ref) => {
|
|
37
|
+
const {
|
|
38
|
+
channel,
|
|
39
|
+
disabled = false,
|
|
40
|
+
isMelodicMode = false,
|
|
41
|
+
mapping,
|
|
42
|
+
onChange,
|
|
43
|
+
value: initialValue,
|
|
44
|
+
...rest
|
|
45
|
+
} = props
|
|
46
|
+
|
|
47
|
+
const options = useMemo(() => {
|
|
48
|
+
if (isMelodicMode) {
|
|
49
|
+
return emptyMapping()
|
|
50
|
+
.map(({ note }) => {
|
|
51
|
+
const midiNoteName = midiToNoteName(note, { sharps: false })
|
|
52
|
+
.replace('b', '♭')
|
|
53
|
+
.replace('#', '♯')
|
|
54
|
+
return `${midiNoteName} (#${note})`
|
|
55
|
+
})
|
|
56
|
+
.map(formattedListEntry)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return (mapping || emptyMapping())
|
|
60
|
+
.map(formattedMapEntry)
|
|
61
|
+
.map(formattedListEntry)
|
|
62
|
+
}, [isMelodicMode, mapping])
|
|
63
|
+
|
|
64
|
+
const [value, setValue] = useStateWithDynamicDefault(initialValue)
|
|
65
|
+
|
|
66
|
+
const handleChange = useCallback(
|
|
67
|
+
(v: number | string) => {
|
|
68
|
+
if (typeof v === 'string') return
|
|
69
|
+
|
|
70
|
+
const possibleNoteNumber = assertRange(v, 128, 0)
|
|
71
|
+
|
|
72
|
+
if (possibleNoteNumber > 0) {
|
|
73
|
+
onChange(possibleNoteNumber)
|
|
74
|
+
}
|
|
75
|
+
setValue(v)
|
|
76
|
+
},
|
|
77
|
+
[onChange, setValue],
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<Select
|
|
82
|
+
{...rest}
|
|
83
|
+
disabled={disabled}
|
|
84
|
+
// isDisabled={disabled}
|
|
85
|
+
onChange={handleChange}
|
|
86
|
+
options={options}
|
|
87
|
+
ref={ref}
|
|
88
|
+
value={value}
|
|
89
|
+
/>
|
|
90
|
+
)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
export default NotePicker
|
|
94
|
+
|
|
95
|
+
export type { NotePickerProps, NotePickerRef }
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Piano as ReactPiano } from 'zds-react-piano'
|
|
2
|
+
import type { WithProviderProps } from '../other/OctavePlayer'
|
|
3
|
+
import SoundfontProvider from '../other/SoundFontProvider'
|
|
4
|
+
|
|
5
|
+
const firstNote = 21
|
|
6
|
+
const lastNote = 108
|
|
7
|
+
const numNotes = lastNote - firstNote - 1
|
|
8
|
+
|
|
9
|
+
const whiteNotes = numNotes * 0.7 // approx
|
|
10
|
+
|
|
11
|
+
const audioContext = new window.AudioContext()
|
|
12
|
+
|
|
13
|
+
type PianoProps = {
|
|
14
|
+
disabled: boolean
|
|
15
|
+
height: number
|
|
16
|
+
width: number
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const PianoPicker = ({ height, width, ...rest }: PianoProps) => {
|
|
20
|
+
const keyWidth = width / whiteNotes + 2
|
|
21
|
+
const keyWidthToHeight = keyWidth / height
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<div>
|
|
25
|
+
<ReactPiano
|
|
26
|
+
keyWidthToHeight={keyWidthToHeight}
|
|
27
|
+
noteRange={{ first: firstNote, last: lastNote }}
|
|
28
|
+
width={width}
|
|
29
|
+
{...rest}
|
|
30
|
+
/>
|
|
31
|
+
</div>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const WithProvider = ({
|
|
36
|
+
format,
|
|
37
|
+
height,
|
|
38
|
+
hostname,
|
|
39
|
+
instrumentName,
|
|
40
|
+
onChange,
|
|
41
|
+
onClick,
|
|
42
|
+
onDoubleClick,
|
|
43
|
+
onKeyMouseEnter,
|
|
44
|
+
onKeyMouseLeave,
|
|
45
|
+
soundfont,
|
|
46
|
+
width,
|
|
47
|
+
}: WithProviderProps) => (
|
|
48
|
+
<SoundfontProvider
|
|
49
|
+
{...{
|
|
50
|
+
audioContext,
|
|
51
|
+
format,
|
|
52
|
+
hostname,
|
|
53
|
+
instrumentName,
|
|
54
|
+
onChange,
|
|
55
|
+
soundfont,
|
|
56
|
+
}}
|
|
57
|
+
render={({ isLoading, playNote, stopNote }) => (
|
|
58
|
+
<PianoPicker
|
|
59
|
+
disabled={isLoading}
|
|
60
|
+
{...{
|
|
61
|
+
height,
|
|
62
|
+
onClick,
|
|
63
|
+
onDoubleClick,
|
|
64
|
+
onKeyMouseEnter,
|
|
65
|
+
onKeyMouseLeave,
|
|
66
|
+
playNote,
|
|
67
|
+
stopNote,
|
|
68
|
+
width,
|
|
69
|
+
}}
|
|
70
|
+
/>
|
|
71
|
+
)}
|
|
72
|
+
/>
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
export default WithProvider
|
|
76
|
+
|
|
77
|
+
export type { PianoProps }
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { forwardRef } from 'react'
|
|
2
|
+
import type { GroupBase, SelectInstance } from 'react-select'
|
|
3
|
+
import Select from './Select'
|
|
4
|
+
import type { Option, SelectProps } from './Select'
|
|
5
|
+
|
|
6
|
+
type Polarity = 0 | 1
|
|
7
|
+
|
|
8
|
+
type PolarityPickerProps = SelectProps<Polarity> & {
|
|
9
|
+
labelOff?: string
|
|
10
|
+
labelOn?: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type PolarityPickerRef = SelectInstance<
|
|
14
|
+
Option<Polarity>,
|
|
15
|
+
false,
|
|
16
|
+
GroupBase<Option<Polarity>>
|
|
17
|
+
>
|
|
18
|
+
|
|
19
|
+
const PolarityPicker = forwardRef<
|
|
20
|
+
SelectInstance<Option<Polarity>, false, GroupBase<Option<Polarity>>>,
|
|
21
|
+
PolarityPickerProps
|
|
22
|
+
>(({ labelOff = 'Normally Off', labelOn = 'Normally On', ...rest }, ref) => {
|
|
23
|
+
const options = [
|
|
24
|
+
{ value: 0, label: labelOff },
|
|
25
|
+
{ value: 1, label: labelOn },
|
|
26
|
+
] as Option<Polarity>[]
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<Select
|
|
30
|
+
{...rest}
|
|
31
|
+
options={options}
|
|
32
|
+
ref={ref}
|
|
33
|
+
/>
|
|
34
|
+
)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
export default PolarityPicker
|
|
38
|
+
|
|
39
|
+
export type { Polarity, PolarityPickerProps, PolarityPickerRef }
|