UNPKG

react-native-piano-keyboard

Version:

A beautiful and customizable piano keyboard component for React Native

182 lines (181 loc) 7.11 kB
"use strict"; 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", }, });