@@ -184,7 +184,7 @@ export const DarkStyled = (props?: Partial<Props>) => ( | |||||
</div> | </div> | ||||
) | ) | ||||
const HasMapComponent = () => { | |||||
const HasMapComponent = (props: any) => { | |||||
const [keyChannels, setKeyChannels] = React.useState<{ key: number; velocity: number; channel: number }[]>([]) | const [keyChannels, setKeyChannels] = React.useState<{ key: number; velocity: number; channel: number }[]>([]) | ||||
const midiAccess = React.useRef<any>(undefined) | const midiAccess = React.useRef<any>(undefined) | ||||
@@ -223,6 +223,7 @@ const HasMapComponent = () => { | |||||
return ( | return ( | ||||
<Wrapper> | <Wrapper> | ||||
<Keyboard | <Keyboard | ||||
{...props} | |||||
startKey={21} | startKey={21} | ||||
endKey={108} | endKey={108} | ||||
keyChannels={keyChannels} | keyChannels={keyChannels} | ||||
@@ -274,6 +275,8 @@ const HasMapComponent = () => { | |||||
export const HasMap = () => <HasMapComponent /> | export const HasMap = () => <HasMapComponent /> | ||||
export const Mirrored = () => <HasMapComponent mirrored /> | |||||
export const Checkbox = (props?: Partial<Props>) => ( | export const Checkbox = (props?: Partial<Props>) => ( | ||||
<Wrapper> | <Wrapper> | ||||
<Keyboard {...props} startKey={21} endKey={108} behavior="checkbox" name="checkbox" /> | <Keyboard {...props} startKey={21} endKey={108} behavior="checkbox" name="checkbox" /> | ||||
@@ -291,3 +294,23 @@ export const Link = (props?: Partial<Props>) => ( | |||||
<Keyboard {...props} startKey={21} endKey={108} behavior="link" href={(key) => `?key=${key}`} /> | <Keyboard {...props} startKey={21} endKey={108} behavior="link" href={(key) => `?key=${key}`} /> | ||||
</Wrapper> | </Wrapper> | ||||
) | ) | ||||
export const Rotated90 = (props?: Partial<Props>) => ( | |||||
<HasMapComponent {...props} orientation={90} width={80} height={600} /> | |||||
) | |||||
export const Rotated180 = (props?: Partial<Props>) => <HasMapComponent {...props} orientation={180} /> | |||||
export const Rotated270 = (props?: Partial<Props>) => ( | |||||
<HasMapComponent {...props} orientation={270} width={80} height={600} /> | |||||
) | |||||
export const Rotated90Mirrored = (props?: Partial<Props>) => ( | |||||
<HasMapComponent {...props} orientation={90} width={80} height={600} mirrored /> | |||||
) | |||||
export const Rotated180Mirrored = (props?: Partial<Props>) => <HasMapComponent {...props} orientation={180} mirrored /> | |||||
export const Rotated270Mirrored = (props?: Partial<Props>) => ( | |||||
<HasMapComponent {...props} orientation={270} width={80} height={600} mirrored /> | |||||
) |
@@ -8,8 +8,7 @@ import DefaultAccidentalKey from '../AccidentalKey/AccidentalKey' | |||||
import DefaultNaturalKey from '../NaturalKey/NaturalKey' | import DefaultNaturalKey from '../NaturalKey/NaturalKey' | ||||
import KeyboardMap from '../KeyboardMap/KeyboardMap' | import KeyboardMap from '../KeyboardMap/KeyboardMap' | ||||
import getKeyBounds from '../../services/getKeyBounds' | import getKeyBounds from '../../services/getKeyBounds' | ||||
const BEHAVIOR = ['link', 'checkbox', 'radio'] as const | |||||
import { BEHAVIORS, OCTAVE_DIVISIONS, ORIENTATIONS } from '../../services/constants' | |||||
export const propTypes = { | export const propTypes = { | ||||
/** | /** | ||||
@@ -22,7 +21,10 @@ export const propTypes = { | |||||
*/ | */ | ||||
endKey: PropTypes.number.isRequired, | endKey: PropTypes.number.isRequired, | ||||
//octaveDivision: PropTypes.number, | |||||
/** | |||||
* Equal parts of an octave. | |||||
*/ | |||||
octaveDivision: PropTypes.oneOf(OCTAVE_DIVISIONS), | |||||
/** | /** | ||||
* Ratio of the length of the accidental keys to the natural keys. | * Ratio of the length of the accidental keys to the natural keys. | ||||
@@ -67,7 +69,7 @@ export const propTypes = { | |||||
/** | /** | ||||
* Behavior of the component when clicking. | * Behavior of the component when clicking. | ||||
*/ | */ | ||||
behavior: PropTypes.oneOf(BEHAVIOR), | |||||
behavior: PropTypes.oneOf(BEHAVIORS), | |||||
/** | /** | ||||
* Name of the component used for forms. | * Name of the component used for forms. | ||||
*/ | */ | ||||
@@ -87,31 +89,25 @@ export const propTypes = { | |||||
* Received velocity when activating the component through the keyboard. | * Received velocity when activating the component through the keyboard. | ||||
*/ | */ | ||||
keyboardVelocity: PropTypes.number, | keyboardVelocity: PropTypes.number, | ||||
/** | |||||
* Orientation of the component. | |||||
*/ | |||||
orientation: PropTypes.oneOf(ORIENTATIONS), | |||||
/** | |||||
* Is the component mirrored? | |||||
*/ | |||||
mirrored: PropTypes.bool, | |||||
} | } | ||||
type Props = PropTypes.InferProps<typeof propTypes> | type Props = PropTypes.InferProps<typeof propTypes> | ||||
/** | /** | ||||
* Component for displaying musical notes in the form of a piano keyboard. | * Component for displaying musical notes in the form of a piano keyboard. | ||||
* @param startKey - MIDI note of the first key. | |||||
* @param endKey - MIDI note of the last key. | |||||
* @param accidentalKeyLengthRatio - Ratio of the length of the accidental keys to the natural keys. | |||||
* @param keyChannels - Current active keys and their channel assignments. | |||||
* @param width - Width of the component. | |||||
* @param keyComponents - Components to use for each kind of key. | |||||
* @param height - Height of the component. | |||||
* @param name - Name of the component used for forms. | |||||
* @param href - Destination of the component upon clicking a key, if behavior is set to 'link'. | |||||
* @param behavior - Behavior of the component when clicking. | |||||
* @param onChange - Event handler triggered upon change in activated keys in the component. | |||||
* @param keyboardMapping - Map from key code to key number, used to activate the component from the keyboard. | |||||
* @param midiInput - Can MIDI input messages activate the component? | |||||
* @param keyboardVelocity - Received velocity when activating the component through the keyboard. | |||||
*/ | */ | ||||
const Keyboard: React.FC<Props> = ({ | const Keyboard: React.FC<Props> = ({ | ||||
startKey, | startKey, | ||||
endKey, | endKey, | ||||
//octaveDivision = 12, | |||||
octaveDivision = 12, | |||||
accidentalKeyLengthRatio = 0.65, | accidentalKeyLengthRatio = 0.65, | ||||
keyChannels = [], | keyChannels = [], | ||||
width = '100%', | width = '100%', | ||||
@@ -124,6 +120,8 @@ const Keyboard: React.FC<Props> = ({ | |||||
href, | href, | ||||
midiInput, | midiInput, | ||||
keyboardVelocity, | keyboardVelocity, | ||||
orientation = 0, | |||||
mirrored = false, | |||||
}) => { | }) => { | ||||
const [clientSide, setClientSide] = React.useState(false) | const [clientSide, setClientSide] = React.useState(false) | ||||
const [clientSideKeys, setClientSideKeys] = React.useState<number[]>([]) | const [clientSideKeys, setClientSideKeys] = React.useState<number[]>([]) | ||||
@@ -144,6 +142,30 @@ const Keyboard: React.FC<Props> = ({ | |||||
}, [startKey, endKey]) | }, [startKey, endKey]) | ||||
const keys = clientSide ? clientSideKeys : generateKeys(startKey, endKey) | const keys = clientSide ? clientSideKeys : generateKeys(startKey, endKey) | ||||
const widthDimension = orientation === 90 || orientation === 270 ? 'height' : 'width' | |||||
const heightDimension = orientation === 90 || orientation === 270 ? 'width' : 'height' | |||||
let leftDirection: string | |||||
let topDirection: string | |||||
switch (orientation) { | |||||
default: | |||||
case 0: | |||||
leftDirection = 'left' | |||||
topDirection = 'top' | |||||
break | |||||
case 90: | |||||
leftDirection = 'bottom' | |||||
topDirection = 'left' | |||||
break | |||||
case 180: | |||||
leftDirection = 'right' | |||||
topDirection = 'bottom' | |||||
break | |||||
case 270: | |||||
leftDirection = 'top' | |||||
topDirection = 'right' | |||||
break | |||||
} | |||||
return ( | return ( | ||||
<React.Fragment> | <React.Fragment> | ||||
@@ -176,7 +198,8 @@ const Keyboard: React.FC<Props> = ({ | |||||
getKeyWidth, | getKeyWidth, | ||||
)(key, left, width) | )(key, left, width) | ||||
const octaveStart = Math.floor(key / 12) * 12 | const octaveStart = Math.floor(key / 12) * 12 | ||||
const octaveEnd = octaveStart + 11 | |||||
const theOctaveDivision = (octaveDivision as number) !== 12 ? 12 : octaveDivision | |||||
const octaveEnd = octaveStart + 12 * (1 - 1 / theOctaveDivision!) | |||||
const octaveLeftBounds = getKeyLeft(octaveStart) | const octaveLeftBounds = getKeyLeft(octaveStart) | ||||
const octaveRightBounds = getKeyLeft(octaveEnd) + getKeyWidth(octaveEnd) | const octaveRightBounds = getKeyLeft(octaveEnd) + getKeyWidth(octaveEnd) | ||||
const components: Record<string, string> = { | const components: Record<string, string> = { | ||||
@@ -202,11 +225,11 @@ const Keyboard: React.FC<Props> = ({ | |||||
data-right-full-bounds={isNatural ? left + width : undefined} | data-right-full-bounds={isNatural ? left + width : undefined} | ||||
style={{ | style={{ | ||||
zIndex: isNatural ? 0 : 2, | zIndex: isNatural ? 0 : 2, | ||||
width: width + '%', | |||||
height: (isNatural ? 100 : 100 * accidentalKeyLengthRatio!) + '%', | |||||
left: left + '%', | |||||
[widthDimension]: width + '%', | |||||
[heightDimension]: (isNatural ? 100 : 100 * accidentalKeyLengthRatio!) + '%', | |||||
[leftDirection]: (mirrored ? 100 - width - left : left) + '%', | |||||
position: 'absolute', | position: 'absolute', | ||||
top: 0, | |||||
[topDirection]: 0, | |||||
cursor: onChange || behavior ? 'pointer' : undefined, | cursor: onChange || behavior ? 'pointer' : undefined, | ||||
color: 'inherit', | color: 'inherit', | ||||
'--opacity-highlight': currentKey !== null ? 1 : 0, | '--opacity-highlight': currentKey !== null ? 1 : 0, | ||||
@@ -238,6 +261,8 @@ const Keyboard: React.FC<Props> = ({ | |||||
keyboardMapping={keyboardMapping} | keyboardMapping={keyboardMapping} | ||||
midiInput={midiInput} | midiInput={midiInput} | ||||
keyboardVelocity={keyboardVelocity} | keyboardVelocity={keyboardVelocity} | ||||
orientation={orientation} | |||||
mirrored={mirrored} | |||||
/> | /> | ||||
)} | )} | ||||
</div> | </div> | ||||
@@ -2,6 +2,7 @@ import * as React from 'react' | |||||
import * as PropTypes from 'prop-types' | import * as PropTypes from 'prop-types' | ||||
import reverseGetKeyFromPoint from '../../services/reverseGetKeyFromPoint' | import reverseGetKeyFromPoint from '../../services/reverseGetKeyFromPoint' | ||||
import { MIDIMessageEvent } from '../../services/midi' | import { MIDIMessageEvent } from '../../services/midi' | ||||
import { ORIENTATIONS } from '../../services/constants' | |||||
const propTypes = { | const propTypes = { | ||||
/** | /** | ||||
@@ -27,17 +28,20 @@ const propTypes = { | |||||
addEventListener: PropTypes.func.isRequired, | addEventListener: PropTypes.func.isRequired, | ||||
removeEventListener: PropTypes.func.isRequired, | removeEventListener: PropTypes.func.isRequired, | ||||
}), | }), | ||||
/** | |||||
* Orientation of the component. | |||||
*/ | |||||
orientation: PropTypes.oneOf(ORIENTATIONS), | |||||
/** | |||||
* Is the component mirrored? | |||||
*/ | |||||
mirrored: PropTypes.bool, | |||||
} | } | ||||
type Props = PropTypes.InferProps<typeof propTypes> | type Props = PropTypes.InferProps<typeof propTypes> | ||||
/** | /** | ||||
* Keyboard map for allowing interactivity with the keyboard. | * Keyboard map for allowing interactivity with the keyboard. | ||||
* @param accidentalKeyLengthRatio - Ratio of the length of the accidental keys to the natural keys. | |||||
* @param onChange - Event handler triggered upon change in activated keys in the component. | |||||
* @param keyboardMapping - Map from key code to key number. | |||||
* @param midiInput - MIDI input for sending MIDI messages to the component. | |||||
* @param keyboardVelocity - Received velocity when activating the component through the keyboard. | |||||
*/ | */ | ||||
const KeyboardMap: React.FC<Props> = ({ | const KeyboardMap: React.FC<Props> = ({ | ||||
accidentalKeyLengthRatio, | accidentalKeyLengthRatio, | ||||
@@ -45,6 +49,8 @@ const KeyboardMap: React.FC<Props> = ({ | |||||
keyboardMapping = {}, | keyboardMapping = {}, | ||||
midiInput, | midiInput, | ||||
keyboardVelocity = 0.75, | keyboardVelocity = 0.75, | ||||
orientation = 0, | |||||
mirrored = false, | |||||
}) => { | }) => { | ||||
const baseRef = React.useRef<HTMLDivElement>(null) | const baseRef = React.useRef<HTMLDivElement>(null) | ||||
const keysOnRef = React.useRef<any[]>([]) | const keysOnRef = React.useRef<any[]>([]) | ||||
@@ -54,46 +60,66 @@ const KeyboardMap: React.FC<Props> = ({ | |||||
e.preventDefault() | e.preventDefault() | ||||
} | } | ||||
const handleMouseDown: React.MouseEventHandler = (e) => { | |||||
if (baseRef.current === null) { | |||||
return | |||||
} | |||||
if (baseRef.current.parentElement === null) { | |||||
return | |||||
} | |||||
if (e.buttons !== 1) { | |||||
return | |||||
} | |||||
const keyData = reverseGetKeyFromPoint(baseRef.current!.parentElement!, accidentalKeyLengthRatio!)( | |||||
e.clientX, | |||||
e.clientY, | |||||
) | |||||
if (keyData! === null) { | |||||
return | |||||
React.useEffect(() => { | |||||
const baseRefCurrent = baseRef.current | |||||
const handleMouseDown = (e: MouseEvent) => { | |||||
if (baseRef.current === null) { | |||||
return | |||||
} | |||||
if (baseRef.current.parentElement === null) { | |||||
return | |||||
} | |||||
if (e.buttons !== 1) { | |||||
return | |||||
} | |||||
e.preventDefault() | |||||
const keyData = reverseGetKeyFromPoint( | |||||
baseRef.current!.parentElement!, | |||||
accidentalKeyLengthRatio!, | |||||
orientation!, | |||||
mirrored!, | |||||
)(e.clientX, e.clientY) | |||||
if (keyData! === null) { | |||||
return | |||||
} | |||||
if (lastVelocity.current === undefined) { | |||||
lastVelocity.current = keyData.velocity > 1 ? 1 : keyData.velocity < 0 ? 0 : keyData.velocity | |||||
} | |||||
keysOnRef.current = [...keysOnRef.current, { ...keyData, velocity: lastVelocity.current, id: -1 }] | |||||
if (typeof onChange! === 'function') { | |||||
onChange(keysOnRef.current) | |||||
} | |||||
} | } | ||||
if (lastVelocity.current === undefined) { | |||||
lastVelocity.current = keyData.velocity > 1 ? 1 : keyData.velocity < 0 ? 0 : keyData.velocity | |||||
if (baseRefCurrent !== null) { | |||||
baseRefCurrent.addEventListener('mousedown', handleMouseDown, { passive: false }) | |||||
} | } | ||||
keysOnRef.current = [...keysOnRef.current, { ...keyData, velocity: lastVelocity.current, id: -1 }] | |||||
if (typeof onChange! === 'function') { | |||||
onChange(keysOnRef.current) | |||||
return () => { | |||||
if (baseRefCurrent !== null) { | |||||
baseRefCurrent.removeEventListener('mousedown', handleMouseDown) | |||||
} | |||||
} | } | ||||
} | |||||
}, [accidentalKeyLengthRatio, onChange, orientation, mirrored]) | |||||
React.useEffect(() => { | React.useEffect(() => { | ||||
const baseRefCurrent = baseRef.current | const baseRefCurrent = baseRef.current | ||||
const handleTouchStart = (e: TouchEvent) => { | const handleTouchStart = (e: TouchEvent) => { | ||||
e.preventDefault() | |||||
if (baseRef.current === null) { | if (baseRef.current === null) { | ||||
return | return | ||||
} | } | ||||
if (baseRef.current.parentElement === null) { | if (baseRef.current.parentElement === null) { | ||||
return | return | ||||
} | } | ||||
e.preventDefault() | |||||
const touches = Array.from(e.changedTouches) | const touches = Array.from(e.changedTouches) | ||||
const touchKeyData = touches.map<[React.Touch, { key: number; velocity: number } | null]>((t) => [ | const touchKeyData = touches.map<[React.Touch, { key: number; velocity: number } | null]>((t) => [ | ||||
t, | t, | ||||
reverseGetKeyFromPoint(baseRef.current!.parentElement!, accidentalKeyLengthRatio!)(t.clientX, t.clientY), | |||||
reverseGetKeyFromPoint( | |||||
baseRef.current!.parentElement!, | |||||
accidentalKeyLengthRatio!, | |||||
orientation!, | |||||
mirrored!, | |||||
)(t.clientX, t.clientY), | |||||
]) | ]) | ||||
const validTouchKeyData = touchKeyData.filter(([, keyData]) => keyData! !== null) | const validTouchKeyData = touchKeyData.filter(([, keyData]) => keyData! !== null) | ||||
validTouchKeyData.forEach(([t, keyData]) => { | validTouchKeyData.forEach(([t, keyData]) => { | ||||
@@ -116,22 +142,24 @@ const KeyboardMap: React.FC<Props> = ({ | |||||
baseRefCurrent.removeEventListener('touchstart', handleTouchStart) | baseRefCurrent.removeEventListener('touchstart', handleTouchStart) | ||||
} | } | ||||
} | } | ||||
}, [accidentalKeyLengthRatio, onChange]) | |||||
}, [accidentalKeyLengthRatio, onChange, orientation, mirrored]) | |||||
React.useEffect(() => { | React.useEffect(() => { | ||||
const handleTouchMove = (e: TouchEvent) => { | const handleTouchMove = (e: TouchEvent) => { | ||||
e.preventDefault() | |||||
if (baseRef.current === null) { | if (baseRef.current === null) { | ||||
return | return | ||||
} | } | ||||
if (baseRef.current.parentElement === null) { | if (baseRef.current.parentElement === null) { | ||||
return | return | ||||
} | } | ||||
e.preventDefault() | |||||
Array.from(e.changedTouches).forEach((t) => { | Array.from(e.changedTouches).forEach((t) => { | ||||
const keyData = reverseGetKeyFromPoint(baseRef.current!.parentElement!, accidentalKeyLengthRatio!)( | |||||
t.clientX, | |||||
t.clientY, | |||||
) | |||||
const keyData = reverseGetKeyFromPoint( | |||||
baseRef.current!.parentElement!, | |||||
accidentalKeyLengthRatio!, | |||||
orientation!, | |||||
mirrored!, | |||||
)(t.clientX, t.clientY) | |||||
if (keyData! === null) { | if (keyData! === null) { | ||||
keysOnRef.current = keysOnRef.current.filter((k) => k.id !== t.identifier) | keysOnRef.current = keysOnRef.current.filter((k) => k.id !== t.identifier) | ||||
if (typeof onChange! === 'function') { | if (typeof onChange! === 'function') { | ||||
@@ -167,11 +195,10 @@ const KeyboardMap: React.FC<Props> = ({ | |||||
return () => { | return () => { | ||||
window.removeEventListener('touchmove', handleTouchMove) | window.removeEventListener('touchmove', handleTouchMove) | ||||
} | } | ||||
}, [accidentalKeyLengthRatio, onChange]) | |||||
}, [accidentalKeyLengthRatio, onChange, orientation, mirrored]) | |||||
React.useEffect(() => { | React.useEffect(() => { | ||||
const handleMouseMove = (e: MouseEvent) => { | const handleMouseMove = (e: MouseEvent) => { | ||||
e.preventDefault() | |||||
if (baseRef.current === null) { | if (baseRef.current === null) { | ||||
return | return | ||||
} | } | ||||
@@ -181,10 +208,13 @@ const KeyboardMap: React.FC<Props> = ({ | |||||
if (e.buttons !== 1) { | if (e.buttons !== 1) { | ||||
return | return | ||||
} | } | ||||
const keyData = reverseGetKeyFromPoint(baseRef.current!.parentElement, accidentalKeyLengthRatio!)( | |||||
e.clientX, | |||||
e.clientY, | |||||
) | |||||
e.preventDefault() | |||||
const keyData = reverseGetKeyFromPoint( | |||||
baseRef.current!.parentElement, | |||||
accidentalKeyLengthRatio!, | |||||
orientation!, | |||||
mirrored!, | |||||
)(e.clientX, e.clientY) | |||||
if (keyData! === null) { | if (keyData! === null) { | ||||
keysOnRef.current = keysOnRef.current.filter((k) => k.id !== -1) | keysOnRef.current = keysOnRef.current.filter((k) => k.id !== -1) | ||||
if (typeof onChange! === 'function') { | if (typeof onChange! === 'function') { | ||||
@@ -215,7 +245,7 @@ const KeyboardMap: React.FC<Props> = ({ | |||||
return () => { | return () => { | ||||
window.removeEventListener('mousemove', handleMouseMove) | window.removeEventListener('mousemove', handleMouseMove) | ||||
} | } | ||||
}, [accidentalKeyLengthRatio, onChange]) | |||||
}, [accidentalKeyLengthRatio, onChange, orientation, mirrored]) | |||||
React.useEffect(() => { | React.useEffect(() => { | ||||
const handleTouchEnd = (e: TouchEvent) => { | const handleTouchEnd = (e: TouchEvent) => { | ||||
@@ -233,43 +263,23 @@ const KeyboardMap: React.FC<Props> = ({ | |||||
} | } | ||||
}) | }) | ||||
} | } | ||||
window.addEventListener('touchcancel', handleTouchEnd) | |||||
window.addEventListener('touchend', handleTouchEnd) | window.addEventListener('touchend', handleTouchEnd) | ||||
return () => { | return () => { | ||||
window.removeEventListener('touchcancel', handleTouchEnd) | |||||
window.removeEventListener('touchend', handleTouchEnd) | window.removeEventListener('touchend', handleTouchEnd) | ||||
} | } | ||||
}, [onChange]) | }, [onChange]) | ||||
React.useEffect(() => { | |||||
const handleTouchCancel = (e: TouchEvent) => { | |||||
if (baseRef.current === null) { | |||||
return | |||||
} | |||||
if (baseRef.current.parentElement === null) { | |||||
return | |||||
} | |||||
Array.from(e.changedTouches).forEach((t) => { | |||||
keysOnRef.current = keysOnRef.current.filter((k) => k.id !== t.identifier) | |||||
lastVelocity.current = undefined | |||||
if (typeof onChange! === 'function') { | |||||
onChange(keysOnRef.current) | |||||
} | |||||
}) | |||||
} | |||||
window.addEventListener('touchcancel', handleTouchCancel) | |||||
return () => { | |||||
window.removeEventListener('touchcancel', handleTouchCancel) | |||||
} | |||||
}, [onChange]) | |||||
React.useEffect(() => { | React.useEffect(() => { | ||||
const handleMouseUp = (e: MouseEvent) => { | const handleMouseUp = (e: MouseEvent) => { | ||||
e.preventDefault() | |||||
if (baseRef.current === null) { | if (baseRef.current === null) { | ||||
return | return | ||||
} | } | ||||
if (baseRef.current.parentElement === null) { | if (baseRef.current.parentElement === null) { | ||||
return | return | ||||
} | } | ||||
e.preventDefault() | |||||
keysOnRef.current = keysOnRef.current.filter((k) => k.id !== -1) | keysOnRef.current = keysOnRef.current.filter((k) => k.id !== -1) | ||||
lastVelocity.current = undefined | lastVelocity.current = undefined | ||||
if (typeof onChange! === 'function') { | if (typeof onChange! === 'function') { | ||||
@@ -281,7 +291,7 @@ const KeyboardMap: React.FC<Props> = ({ | |||||
return () => { | return () => { | ||||
window.removeEventListener('mouseup', handleMouseUp) | window.removeEventListener('mouseup', handleMouseUp) | ||||
} | } | ||||
}, [accidentalKeyLengthRatio, onChange]) | |||||
}, [onChange]) | |||||
React.useEffect(() => { | React.useEffect(() => { | ||||
const baseRefComponent = baseRef.current | const baseRefComponent = baseRef.current | ||||
@@ -295,11 +305,9 @@ const KeyboardMap: React.FC<Props> = ({ | |||||
} | } | ||||
const { [e.code]: key = null } = theKeyboardMapping | const { [e.code]: key = null } = theKeyboardMapping | ||||
if (key === null) { | if (key === null) { | ||||
return | return | ||||
} | } | ||||
if (keysOnRef.current.some((k) => k.key === key && k.id === -2)) { | if (keysOnRef.current.some((k) => k.key === key && k.id === -2)) { | ||||
return | return | ||||
} | } | ||||
@@ -357,10 +365,11 @@ const KeyboardMap: React.FC<Props> = ({ | |||||
let velocity: number | let velocity: number | ||||
switch (arg0 & 0b11110000) { | switch (arg0 & 0b11110000) { | ||||
case 0b10010000: | |||||
case 0b10010000: // Note On | |||||
velocity = arg2 & 0b01111111 | velocity = arg2 & 0b01111111 | ||||
key = arg1 & 0b01111111 | key = arg1 & 0b01111111 | ||||
if (velocity > 0) { | if (velocity > 0) { | ||||
// some MIDI inputs set note off as simply note on with zero velocity | |||||
keysOnRef.current = [ | keysOnRef.current = [ | ||||
...keysOnRef.current, | ...keysOnRef.current, | ||||
{ | { | ||||
@@ -376,7 +385,7 @@ const KeyboardMap: React.FC<Props> = ({ | |||||
onChange(keysOnRef.current) | onChange(keysOnRef.current) | ||||
} | } | ||||
break | break | ||||
case 0b10000000: | |||||
case 0b10000000: // Note off | |||||
key = arg1 & 0b01111111 | key = arg1 & 0b01111111 | ||||
keysOnRef.current = keysOnRef.current.filter((k) => k.key !== key) | keysOnRef.current = keysOnRef.current.filter((k) => k.key !== key) | ||||
if (typeof onChange! === 'function') { | if (typeof onChange! === 'function') { | ||||
@@ -412,7 +421,6 @@ const KeyboardMap: React.FC<Props> = ({ | |||||
}} | }} | ||||
onContextMenu={preventDefault} | onContextMenu={preventDefault} | ||||
onDragStart={preventDefault} | onDragStart={preventDefault} | ||||
onMouseDown={handleMouseDown} | |||||
tabIndex={0} | tabIndex={0} | ||||
/> | /> | ||||
) | ) | ||||
@@ -87,3 +87,9 @@ export const KEY_OFFSETS = [ | |||||
export const ACCIDENTAL_KEY_TO_NATURAL_KEY_WIDTH_RATIO = 9 / 16 | export const ACCIDENTAL_KEY_TO_NATURAL_KEY_WIDTH_RATIO = 9 / 16 | ||||
// export const ACCIDENTAL_KEY_TO_NATURAL_KEY_WIDTH_RATIO = 13 / 23 | // export const ACCIDENTAL_KEY_TO_NATURAL_KEY_WIDTH_RATIO = 13 / 23 | ||||
export const BEHAVIORS = ['link', 'checkbox', 'radio'] as const | |||||
export const OCTAVE_DIVISIONS = [12, 17, 19, 21, 24, 36] as const | |||||
export const ORIENTATIONS = [0, 90, 180, 270] as const |
@@ -1,16 +1,38 @@ | |||||
type ReverseGetKeyFromPoint = ( | type ReverseGetKeyFromPoint = ( | ||||
baseElement: HTMLElement, | baseElement: HTMLElement, | ||||
accidentalKeyLengthRatio: number, | accidentalKeyLengthRatio: number, | ||||
orientation: number, | |||||
mirrored: boolean, | |||||
) => (clientX: number, clientY?: number) => { key: number; velocity: number } | null | ) => (clientX: number, clientY?: number) => { key: number; velocity: number } | null | ||||
const reverseGetKeyFromPoint: ReverseGetKeyFromPoint = (baseElement, accidentalKeyLengthRatio) => { | |||||
const reverseGetKeyFromPoint: ReverseGetKeyFromPoint = ( | |||||
baseElement, | |||||
accidentalKeyLengthRatio, | |||||
orientation, | |||||
mirrored, | |||||
) => { | |||||
const isRealTopFlipped = orientation === 180 || orientation === 270 | |||||
const isRealLeftFlipped = orientation === 90 || orientation === 180 | |||||
const isVertical = orientation === 90 || orientation === 270 | |||||
const { top, left, width, height } = baseElement.getBoundingClientRect() | const { top, left, width, height } = baseElement.getBoundingClientRect() | ||||
const realWidth = isVertical ? height : width | |||||
const realHeight = isVertical ? width : height | |||||
const realLeft = isVertical ? top : left | |||||
const realTop = isVertical ? left : top | |||||
return (clientX, clientY = top) => { | return (clientX, clientY = top) => { | ||||
const realTop = clientY - top | |||||
const realLeft = clientX - left | |||||
const realClientX = isVertical ? clientY : clientX | |||||
const realClientY = isVertical ? clientX : clientY | |||||
const touchTop = isRealTopFlipped ? realHeight - realClientY + realTop : realClientY - realTop | |||||
const touchLeft = mirrored | |||||
? isRealLeftFlipped | |||||
? realClientX - realLeft | |||||
: realWidth - realClientX + realLeft | |||||
: isRealLeftFlipped | |||||
? realWidth - realClientX + realLeft | |||||
: realClientX - realLeft | |||||
// convert the clientX to units in which keys are displayed (percentage) | // convert the clientX to units in which keys are displayed (percentage) | ||||
const leftInKeyUnits = (realLeft / width) * 100 | |||||
const maybeAccidental = realTop <= height * accidentalKeyLengthRatio! | |||||
const leftInKeyUnits = (touchLeft / realWidth) * 100 | |||||
const maybeAccidental = touchTop <= realHeight * accidentalKeyLengthRatio! | |||||
const keysArray = Array.from(baseElement.children) as HTMLElement[] | const keysArray = Array.from(baseElement.children) as HTMLElement[] | ||||
const keys = keysArray.filter((c) => 'key' in c.dataset) | const keys = keysArray.filter((c) => 'key' in c.dataset) | ||||
const currentOctave = keys.filter((k) => { | const currentOctave = keys.filter((k) => { | ||||
@@ -50,7 +72,7 @@ const reverseGetKeyFromPoint: ReverseGetKeyFromPoint = (baseElement, accidentalK | |||||
} | } | ||||
const { height: keyHeight } = key.getBoundingClientRect() | const { height: keyHeight } = key.getBoundingClientRect() | ||||
return { | return { | ||||
velocity: realTop / keyHeight, | |||||
velocity: touchTop / keyHeight, | |||||
key: Number(key.dataset.key), | key: Number(key.dataset.key), | ||||
} | } | ||||
} | } | ||||