react-native-piano-keyboard
Version:
A beautiful and customizable piano keyboard component for React Native
182 lines (181 loc) • 7.11 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PianoKeyboard = void 0;
const expo_image_1 = require("expo-image");
const react_1 = require("react");
const react_native_1 = require("react-native");
const tonal_1 = require("tonal");
const MidiProvider_1 = require("./MidiProvider");
const utils_1 = require("../utils");
// White key image w : h = 53 : 195
const NOTE_WHITE_WIDTH = 53;
const NOTE_WHITE_HEIGHT = 195;
// Black key image w : h = 29 : 125
const NOTE_BLACK_WIDTH = 29;
const NOTE_BLACK_HEIGHT = 125;
const ENABLE_SUSTAIN = true;
function isBlackKey(note) {
return note.includes("#") || note.includes("b");
}
const PianoKeyboard = ({ startKey = "C2", endKey = "C4", keyDisabled, onPressKey, }) => {
const [pianoKeys, setPianoKeys] = (0, react_1.useState)([]);
const [keyboardMetric, setKeyboardMetric] = (0, react_1.useState)({ width: 0, height: 0 });
const { width: windowWidth } = (0, react_native_1.useWindowDimensions)();
const { triggerAttackRelease, triggerRelease } = (0, MidiProvider_1.useMidi)();
const getOffset = (noteName) => {
var offset = 0;
switch (noteName[0]) {
case "D":
offset = 3.5;
break;
case "E":
offset = -3.5;
break;
case "G":
offset = 3.5;
break;
case "A":
offset = 0;
break;
case "B":
offset = -3.5;
break;
}
return NOTE_BLACK_WIDTH / 2 + offset;
};
const getKey = (midi, width) => {
const noteName = tonal_1.Note.fromMidi(midi);
const isBlack = tonal_1.Note.accidentals(noteName);
if (isBlack) {
return {
width: 0,
key: {
isWhite: false,
midi: midi,
note: noteName,
left: width - getOffset(noteName),
},
};
}
else {
return {
width: NOTE_WHITE_WIDTH,
key: {
isWhite: true,
midi: midi,
note: noteName,
left: width,
},
};
}
};
const getKeyImage = (key, pressed) => {
var image = undefined;
if (key.isWhite) {
image = pressed
? require("../assets/images/white_key_pressed.png")
: require("../assets/images/white_key.png");
}
else {
image = pressed
? require("../assets/images/black_key_pressed.png")
: require("../assets/images/black_key.png");
}
return image;
};
const initializeKeyboard = () => {
const startMidi = tonal_1.Note.midi(startKey) - (isBlackKey(startKey) ? 1 : 0);
const endMidi = tonal_1.Note.midi(endKey) + (isBlackKey(endKey) ? 1 : 0);
const midiRange = [...Array(endMidi - startMidi + 1).keys()].map((_, index) => startMidi + index);
const { width, keys } = midiRange.reduce((keyboard, midi) => {
var keys = [...keyboard.keys];
const { width, key } = getKey(midi, keyboard.width);
keys.push(key);
return {
width: keyboard.width + width,
keys: keys,
};
}, { width: 0, keys: [] });
setPianoKeys(keys);
setKeyboardMetric({ width: width, height: NOTE_WHITE_HEIGHT });
};
const onPressKeyIn = (key) => () => __awaiter(void 0, void 0, void 0, function* () {
if (keyDisabled) {
return;
}
onPressKey(key.note);
triggerAttackRelease(key.note, "1");
});
const onPressKeyOut = (key) => () => __awaiter(void 0, void 0, void 0, function* () {
if (!ENABLE_SUSTAIN) {
triggerRelease(key.note, "+0.3");
}
});
const renderPianoKey = (key, index) => {
const width = key.isWhite ? NOTE_WHITE_WIDTH : NOTE_BLACK_WIDTH;
const height = key.isWhite ? NOTE_WHITE_HEIGHT : NOTE_BLACK_HEIGHT;
const keyStyle = {
left: key.left,
width,
height,
zIndex: key.isWhite ? 1 : 5,
};
return (<react_native_1.Pressable key={index.toString()} style={[styles.key, keyStyle]} disabled={keyDisabled} onPressIn={onPressKeyIn(key)} onPressOut={onPressKeyOut(key)} android_disableSound>
{({ pressed }) => (<react_native_1.View style={styles.keyContent}>
<expo_image_1.Image style={styles.keyImage} source={getKeyImage(key, pressed)} {...(react_native_1.Platform.OS === "web" && { draggable: false })}/>
<react_native_1.Text style={styles.keyNote}>{key.isWhite ? key.note : ""}</react_native_1.Text>
</react_native_1.View>)}
</react_native_1.Pressable>);
};
(0, react_1.useEffect)(() => {
initializeKeyboard();
}, []);
return (<react_native_1.View style={styles.container}>
<expo_image_1.Image style={[styles.topImage]} source={require("../assets/images/Top.png")} contentFit="fill" {...(react_native_1.Platform.OS === "web" && { draggable: false })}/>
<react_native_1.ScrollView horizontal showsHorizontalScrollIndicator={false} style={(0, utils_1.isWeb)() && keyboardMetric.width < windowWidth ? styles.scrollView : {}}>
<react_native_1.View style={{ width: keyboardMetric.width, height: keyboardMetric.height }}>
{pianoKeys.map(renderPianoKey)}
</react_native_1.View>
</react_native_1.ScrollView>
<react_native_1.View style={styles.bottom}/>
</react_native_1.View>);
};
exports.PianoKeyboard = PianoKeyboard;
const styles = react_native_1.StyleSheet.create({
container: {
backgroundColor: "#1A1A1A",
},
topImage: {
height: 60,
},
scrollView: {
alignSelf: "center",
},
key: {
position: "absolute",
borderRightWidth: 0,
},
keyContent: {
flex: 1,
alignItems: "center",
justifyContent: "flex-end",
},
keyImage: Object.assign({}, react_native_1.StyleSheet.absoluteFillObject),
keyNote: {
marginBottom: 5,
userSelect: "none",
},
bottom: {
height: 10,
backgroundColor: "#1A1A1A",
},
});