Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[wip] use canvaskit-js #2

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"@expo/metro-runtime": "^3.1.2",
"@shopify/react-native-skia": "^0.1.240",
"@types/react": "~18.2.45",
"canvaskit-js": "https://bit.ly/canvaskit-js_v210",
"canvaskit-js": "2.1.4",
"clsx": "^2.1.0",
"expo": "~50.0.4",
"expo-constants": "~15.4.5",
Expand Down
90 changes: 52 additions & 38 deletions src/components/NoteRoll.tsx
Original file line number Diff line number Diff line change
@@ -1,46 +1,49 @@
import {
Group, Paint, Rect, RoundedRect,
Group, multiply4, processTransform3d, translate,
} from '@shopify/react-native-skia';
import {
Easing, SharedValue, useDerivedValue, useSharedValue, withDelay, withTiming,
} from 'react-native-reanimated';
import { memo, useEffect } from 'react';
import React, { memo, useEffect } from 'react';
import colors from 'tailwindcss/colors';
import {
gameWidth, gameHeight, pianoKeyboardHeight, countdownBars, getDistFromBars, getTimeFromBars, keyWidth, keyNoteColors, accidentalNoteColors, isGamePlaying, getDurationInBars,
} from '../utils/utils';
import { KeysState } from '../hooks/useKeyboard';
import { accidentalNames, keyNames, noteToKeyboardKey } from './PianoKeyboard';
import { PlayMode } from './PlayingUI';
import { SongData } from '@/utils/songs';
import { Plane } from './Plane';

const noteStrokeWidth = 8;

const NoteRoll = ({
keysState,
songData,
noteRollY,
}:{
}: {
keysState: KeysState,
songData: SongData
noteRollY: SharedValue<number>,
}) => {
const noteRollTransform = useDerivedValue(() => [{ translateY: noteRollY?.value }]);

const perspectiveRollIn = useSharedValue(0);
const threeDRollTransform = useDerivedValue(() => [
// Go to the top of the piano keys, horizontally centered
{ translateX: gameWidth / 2 },
{ translateY: gameHeight - pianoKeyboardHeight },

// Apply the perspective effect
{ perspective: perspectiveRollIn.value },
{ rotateX: Math.PI / 10 },

// Go back to the top of the screen
{ translateX: -gameWidth / 2 },
{ translateY: -gameHeight + pianoKeyboardHeight },
]);
const matrix = useDerivedValue(() => {
return processTransform3d([
{ translateX: gameWidth / 2 },
{ translateY: gameHeight - pianoKeyboardHeight },

// Apply the perspective effect
{ perspective: perspectiveRollIn.value },
{ rotateX: Math.PI / 10 },

// Go back to the top of the screen
{ translateX: -gameWidth / 2 },
{ translateY: -gameHeight + pianoKeyboardHeight },
]);
});
const translatedMatrix = useDerivedValue(() => {
return multiply4(matrix.value, translate(0, noteRollY.value));
});

useEffect(() => {
perspectiveRollIn.value = 0;
Expand All @@ -54,9 +57,9 @@ const NoteRoll = ({
};
}, [songData.name]);

return <Group transform={threeDRollTransform}>
return <Group>
{/* Create a line at the center of each piano key black key and a colored bg if need be ! */}
{ keysState && [...Array(11)].map((_, i) => {
{keysState && [...Array(11)].map((_, i) => {
const xPos = i * (keyWidth);

const keyPressed = keysState[keyNames[i]];
Expand All @@ -70,17 +73,17 @@ const NoteRoll = ({

return <Group key={`lines_${i}`}>
{/* BG */}
{ (i < 10) && <Rect key={`bg_${i}`} x={xPos} y={yPos} width={keyWidth} height={height} color={ (keyPressed) ? keyNoteColors[i] : colors.neutral[950] } opacity={ (keyPressed) ? 0.1 : 1} /> }
{(i < 10) && <Plane key={`bg_${i}`} matrix={matrix} x={xPos} y={yPos} width={keyWidth} height={height} color={(keyPressed) ? keyNoteColors[i] : colors.neutral[950]} opacity={(keyPressed) ? 0.1 : 1} />}

{/* Lines */}
<Rect key={`line_${i}`} x={xPos} y={yPos} width={(accidentalPressed) ? 2 : 1} height={height} color={(accidentalPressed) ? accidentalNoteColors[i - 1] : defaultAccidentalColor} />
<Plane key={`line_${i}`} x={xPos} y={yPos} matrix={matrix} width={(accidentalPressed) ? 2 : 1} height={height} color={(accidentalPressed) ? accidentalNoteColors[i - 1] : defaultAccidentalColor} />

</Group>;
}) }
})}

{/* Draw the notes for each key (black & white 🎹) */}
<Group transform={noteRollTransform}>
{ songData?.notes?.map((note, i) => {
<Group>
{songData?.notes?.map((note, i) => {
if (note.noteName) {
const yOfKeyboardHeight = gameHeight - pianoKeyboardHeight;

Expand All @@ -89,7 +92,7 @@ const NoteRoll = ({
const noteIndex = keyNames.indexOf(keyboardKey);
const noteAccidentalIndex = accidentalNames.indexOf(keyboardKey);

const roundedRectParams:{
const roundedRectParams: {
xPos?: number,
yPos?: number,
width?: number,
Expand All @@ -111,22 +114,33 @@ const NoteRoll = ({
return <></>;
}

return <RoundedRect
key={`note_${i}`}
x={roundedRectParams.xPos}
y={roundedRectParams.yPos}
width={roundedRectParams.width}
height={getDistFromBars(note.durationInBars, songData.bpm) - noteStrokeWidth}
r={5}
>
<Paint color={ roundedRectParams.color } style="stroke" strokeWidth={noteStrokeWidth} opacity={0.5} />
<Paint color={ roundedRectParams.color } />
</RoundedRect>;
return (
<React.Fragment key={`note_${i}`}>
<Plane
matrix={translatedMatrix}
x={roundedRectParams.xPos}
y={roundedRectParams.yPos}
width={roundedRectParams.width}
height={getDistFromBars(note.durationInBars, songData.bpm) - noteStrokeWidth}
r={5}
color={roundedRectParams.color}
/>
<Plane
matrix={translatedMatrix}
x={roundedRectParams.xPos}
y={roundedRectParams.yPos}
width={roundedRectParams.width}
height={getDistFromBars(note.durationInBars, songData.bpm) - noteStrokeWidth}
r={5}
color={roundedRectParams.color} style="stroke" strokeWidth={noteStrokeWidth} opacity={0.5}
/>
</React.Fragment>
);
}

// ELSE: no noteName, so it's a rest
return <></>;
}) }
})}
</Group>
</Group>;
};
Expand Down
43 changes: 25 additions & 18 deletions src/components/PianoKeyboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const PianoKeyboard = ({
disableAnimation,
showNoteNames,
hideSideBackground,
}:{
}: {
keysState: KeysState,
songName: string,
disableAnimation?: boolean,
Expand Down Expand Up @@ -72,10 +72,10 @@ const PianoKeyboard = ({
transform={scrollInTransform}
>
{/* BG */}
{ !hideSideBackground && <Rect x={-(screenWidth - gameWidth) / 2 } y={gameHeight - pianoKeyboardHeight - keyStrokeWidth / 2} width={screenWidth} height={pianoKeyboardHeight + keyStrokeWidth / 2} color={ colors.neutral[950] } />}
{!hideSideBackground && <Rect x={-(screenWidth - gameWidth) / 2} y={gameHeight - pianoKeyboardHeight - keyStrokeWidth / 2} width={screenWidth} height={pianoKeyboardHeight + keyStrokeWidth / 2} color={colors.neutral[950]} />}

{/* Draw 11 White keys using a loop */}
{ keysState && [...Array(numberOfWhiteKeys)].map((_, i) => {
{keysState && [...Array(numberOfWhiteKeys)].map((_, i) => {
const xPos = i * (gameWidth / numberOfWhiteKeys);
const yPos = gameHeight - pianoKeyboardHeight;
const keyName = keyNames[i];
Expand All @@ -89,36 +89,43 @@ const PianoKeyboard = ({

return <Group key={`pianokey_${i}`}>
{/* White Key */}

<RoundedRect x={xPos} y={yPos} width={keyWidth} height={pianoKeyboardHeight} r={5} color={colors.neutral[950]} style="stroke" strokeWidth={keyStrokeWidth}>
</RoundedRect>
<RoundedRect x={xPos} y={yPos} width={keyWidth} height={pianoKeyboardHeight} r={5}>
<Paint color={ colors.neutral[950] } style="stroke" strokeWidth={keyStrokeWidth} />
{/* <Paint color={ colors.neutral[950] } style="stroke" strokeWidth={keyStrokeWidth} /> */}
<LinearGradient
start={vec(xPos, yPos)}
end={vec(xPos, yPos + pianoKeyboardHeight)}
colors={[whiteKeyColor, (keyState) ? '#DDDDDD' : '#EDEDED']}
// Colorful option:
// colors={(keyState) ? [keyNoteColors[i], accidentalNoteColors[i]] : [whiteKeyColor, '#EDEDED']}
// Colorful option:
// colors={(keyState) ? [keyNoteColors[i], accidentalNoteColors[i]] : [whiteKeyColor, '#EDEDED']}
/>

</RoundedRect>

{/* Accidental (if there's one) */}
{ hasAnAccidentalBefore && <RoundedRect x={xPos - keyWidth / 4} y={yPos} width={keyWidth / 2} height={pianoKeyboardHeight / 2} r={5}>
<Paint color={ colors.neutral[950] } style="stroke" strokeWidth={keyStrokeWidth} />
<LinearGradient
start={vec(xPos, yPos)}
end={vec(xPos, yPos + pianoKeyboardHeight)}
colors={[colors.neutral[950], (accidentalState) ? '#222' : '#444']}
/>
</RoundedRect> }
{hasAnAccidentalBefore && <>
<RoundedRect x={xPos - keyWidth / 4} y={yPos} width={keyWidth / 2} height={pianoKeyboardHeight / 2} r={5}>
<LinearGradient
start={vec(xPos, yPos)}
end={vec(xPos, yPos + pianoKeyboardHeight)}
colors={[colors.neutral[950], (accidentalState) ? '#222' : '#444']}
/>
</RoundedRect>
<RoundedRect x={xPos - keyWidth / 4} y={yPos} width={keyWidth / 2} height={pianoKeyboardHeight / 2} r={5} color={colors.neutral[950]} style="stroke" strokeWidth={keyStrokeWidth}>
</RoundedRect>
</>}

{/* Draw the note names (white & black keys! 🎹) */}
{ showNoteNames && (screenWidth > 600) && <Group>
{ hasAnAccidentalBefore && <Text x={xPos - noteNameFontSize / 2.5} y={gameHeight - pianoKeyboardHeight / 2 - noteNameFontSize * ((accidentalState) ? 8 / 10 : 1)} text={accidentalName} font={noteNameFont} color={(accidentalState) ? accidentalNoteColors[i - 1] : whiteKeyColor} /> }
{showNoteNames && (screenWidth > 600) && <Group>
{hasAnAccidentalBefore && <Text x={xPos - noteNameFontSize / 2.5} y={gameHeight - pianoKeyboardHeight / 2 - noteNameFontSize * ((accidentalState) ? 8 / 10 : 1)} text={accidentalName} font={noteNameFont} color={(accidentalState) ? accidentalNoteColors[i - 1] : whiteKeyColor} />}
<Text x={xPos + keyWidth / 2 - noteNameFontSize / 4} y={gameHeight - noteNameFontSize * ((keyState) ? 8 / 10 : 1)} text={keyName} font={noteNameFont} />
</Group>}
</Group>;
}) }
})}
</Group>
);
};

export default memo(PianoKeyboard);
export default PianoKeyboard;
36 changes: 36 additions & 0 deletions src/components/Plane.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Matrix4, PaintProps, Path, SkRect, Skia, TransformProps, Transforms3d, mapPoint3d, processTransform, processTransform3d } from "@shopify/react-native-skia";
import { SharedValue, useDerivedValue } from "react-native-reanimated";

const addRect = (p: Skia.Path, rect: SkRect) => {

};

interface PlaneProps extends PaintProps {
x: number;
y: number;
width: number;
height: number;
matrix: SharedValue<Matrix4>;
r?: number;
}

export const Plane = ({ x, y, width, height, matrix, r = 0, color, opacity, ...props }: PlaneProps) => {
const path = useDerivedValue(() => {
const p = Skia.Path.Make();
const rct = Skia.XYWHRect(x, y, width, height);
if (r > 0) {
p.addRRect(Skia.RRectXY(rct, r, r));
} else {
p.addRect(rct);
}
p.transform(matrix.value)
return p;
});
const cl = Skia.Color(color);
cl[3] = opacity;
return (
<>
<Path path={path} color={cl} {...props} />
</>
);
};
5 changes: 2 additions & 3 deletions src/components/PlayingUI.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -212,10 +212,9 @@ const PlayingUI = ({
{ translateX: (screenWidth - gameWidth) / 2 },
{ translateY: (screenHeight - gameHeight) / 2 },
]}>
<NoteRoll {...{
playMode, keysState, songData, noteRollY,
}} />
<NoteRoll {...{playMode, keysState, songData, noteRollY }} />
<PianoKeyboard keysState={keysState} songName={songData.name} />

</Group>
</Canvas>
</GestureDetector>
Expand Down
22 changes: 11 additions & 11 deletions src/index.web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@ import { App } from 'expo-router/build/qualified-entry';
import { renderRootComponent } from 'expo-router/build/renderRootComponent';

// RN Skia (WASM)
import { LoadSkiaWeb } from '@shopify/react-native-skia/lib/module/web';
// import { LoadSkiaWeb } from '@shopify/react-native-skia/lib/module/web';

LoadSkiaWeb({
locateFile: (file) => `/static/js/${file}`,
}).then(async () => {
renderRootComponent(App);
});
// LoadSkiaWeb({
// locateFile: (file) => `/static/js/${file}`,
// }).then(async () => {
// renderRootComponent(App);
// });

// RN Skia (CanvasKitJS)
// import { CanvasKitJS } from 'canvaskit-js';
// (async () => {
// global.CanvasKit = CanvasKitJS.getInstance();
// renderRootComponent(App);
// })();
import { CanvasKitJS } from 'canvaskit-js';
(async () => {
global.CanvasKit = CanvasKitJS.getInstance();
renderRootComponent(App);
})();
15 changes: 8 additions & 7 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2262,10 +2262,10 @@
component-type "^1.2.1"
join-component "^1.1.0"

"@shopify/react-native-skia@^0.1.237":
version "0.1.240"
resolved "https://registry.yarnpkg.com/@shopify/react-native-skia/-/react-native-skia-0.1.240.tgz#4bd88defc277175eadb52465f070e2df67909483"
integrity sha512-KcmmGiC4eHaVR1Erx5LCR6OUflLW9YN7fEuf3FOt0j7hvMbX3QZDXfqRP0D17jjh4W+BO26+YM4vxm6V7+htkQ==
"@shopify/react-native-skia@^0.1.240":
version "0.1.241"
resolved "https://registry.yarnpkg.com/@shopify/react-native-skia/-/react-native-skia-0.1.241.tgz#592471a565f3a7792e03a453eaf922518dd994e5"
integrity sha512-jQFJHiNUVONTGPPnL7LIJc3kg6x4PBPh3m7NAJuTxuhB8VOSVlQR3UWd4tDHHJgj7otG3S2dbHD7+bftxM5cFA==
dependencies:
canvaskit-wasm "0.39.1"
react-reconciler "0.27.0"
Expand Down Expand Up @@ -2968,9 +2968,10 @@ caniuse-lite@^1.0.30001580:
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001581.tgz#0dfd4db9e94edbdca67d57348ebc070dece279f4"
integrity sha512-whlTkwhqV2tUmP3oYhtNfaWGYHDdS3JYFQBKXxcUR9qqPWsRhFHhoISO2Xnl/g0xyKzht9mI1LZpiNWfMzHixQ==

"canvaskit-js@https://bit.ly/canvaskit-js_v210":
version "2.1.0"
resolved "https://bit.ly/canvaskit-js_v210#fbc8b5eba27e69ec725fefc8ac88cab289d3c469"
[email protected]:
version "2.1.4"
resolved "https://registry.yarnpkg.com/canvaskit-js/-/canvaskit-js-2.1.4.tgz#ad515cb2d59b47f4c69c306b6612271625ee0672"
integrity sha512-FbeP+mrm5flCQToQckgT2ZJkw5CEieiyHnZ9TRPcXR16nmcH1bYk7mK+LysIcKgONwIY0jsgnoxYxRYIE6N2Wg==

[email protected]:
version "0.39.1"
Expand Down