From c9ab84f1af6ee32085dfca01041cc0c93c5a60c6 Mon Sep 17 00:00:00 2001 From: Maciej Makowski <120780663+maciejmakowski2003@users.noreply.github.com> Date: Tue, 7 Jan 2025 12:54:57 +0100 Subject: [PATCH] Fix/jsi and ts bugs (#244) * feat: added host functions caching * refactor: removed unused components and moved common files to common example folders and format * fix: fixed metronome, removed unused files and moved common files * fix: back to previous version of hihat * fix: fixed start/stop mechanism bug --------- Co-authored-by: Maciej Makowski --- apps/common-app/src/components/Step.tsx | 46 ------ apps/common-app/src/components/Steps.tsx | 56 ------- apps/common-app/src/components/index.ts | 2 - .../src/examples/DrumMachine/DrumMachine.tsx | 10 +- .../src/examples/DrumMachine/Grid.tsx | 2 +- .../examples/DrumMachine/NotesHighlight.tsx | 2 +- .../src/examples/DrumMachine/PatternShape.tsx | 2 +- .../src/examples/DrumMachine/PlayButton.tsx | 2 +- .../src/examples/DrumMachine/constants.ts | 2 +- .../src/examples/DrumMachine/instruments.ts | 2 +- .../src/examples/DrumMachine/presets.ts | 2 +- .../src/examples/DrumMachine/types.ts | 39 ----- .../src/examples/DrumMachine/useGestures.ts | 2 +- .../src/examples/Metronome/Metronome.tsx | 155 ++++++------------ .../src/examples/Metronome/patterns.ts | 102 ++++++++++++ .../src/examples/SharedUtils/Scheduler.ts | 79 --------- .../src/examples/SharedUtils/index.ts | 6 - apps/common-app/src/types.ts | 48 +++++- .../soundEngines/Clap.ts | 0 .../soundEngines/HiHat.ts | 0 .../soundEngines/Kick.ts | 0 .../soundEngines/MetronomeSound.ts | 0 .../soundEngines/SoundEngine.ts | 0 .../DrumMachine => utils}/usePlayer.tsx | 15 +- .../common/cpp/core/AudioBufferSourceNode.cpp | 2 + .../cpp/core/AudioScheduledSourceNode.cpp | 8 + .../cpp/core/AudioScheduledSourceNode.h | 5 +- .../common/cpp/core/OscillatorNode.cpp | 2 + .../common/cpp/jsi/JsiHostObject.cpp | 65 ++++---- .../common/cpp/jsi/JsiHostObject.h | 18 +- .../common/cpp/jsi/RuntimeAwareCache.h | 57 +++++++ .../cpp/jsi/RuntimeLifecycleMonitor.cpp | 61 +++++++ .../common/cpp/jsi/RuntimeLifecycleMonitor.h | 32 ++++ 33 files changed, 429 insertions(+), 395 deletions(-) delete mode 100644 apps/common-app/src/components/Step.tsx delete mode 100644 apps/common-app/src/components/Steps.tsx delete mode 100644 apps/common-app/src/examples/DrumMachine/types.ts create mode 100644 apps/common-app/src/examples/Metronome/patterns.ts delete mode 100644 apps/common-app/src/examples/SharedUtils/Scheduler.ts delete mode 100644 apps/common-app/src/examples/SharedUtils/index.ts rename apps/common-app/src/{examples/SharedUtils => utils}/soundEngines/Clap.ts (100%) rename apps/common-app/src/{examples/SharedUtils => utils}/soundEngines/HiHat.ts (100%) rename apps/common-app/src/{examples/SharedUtils => utils}/soundEngines/Kick.ts (100%) rename apps/common-app/src/{examples/SharedUtils => utils}/soundEngines/MetronomeSound.ts (100%) rename apps/common-app/src/{examples/SharedUtils => utils}/soundEngines/SoundEngine.ts (100%) rename apps/common-app/src/{examples/DrumMachine => utils}/usePlayer.tsx (89%) create mode 100644 packages/react-native-audio-api/common/cpp/jsi/RuntimeAwareCache.h create mode 100644 packages/react-native-audio-api/common/cpp/jsi/RuntimeLifecycleMonitor.cpp create mode 100644 packages/react-native-audio-api/common/cpp/jsi/RuntimeLifecycleMonitor.h diff --git a/apps/common-app/src/components/Step.tsx b/apps/common-app/src/components/Step.tsx deleted file mode 100644 index 3487e90f..00000000 --- a/apps/common-app/src/components/Step.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { FC } from 'react'; -import { StyleSheet, Pressable } from 'react-native'; - -import { colors, layout } from '../styles'; - -interface StepProps { - id: number; - active: boolean; - onClick: (idx: number) => void; -} - -const Step: FC = (props) => { - const { id, active, onClick } = props; - - return ( - onClick(id)} - style={({ pressed }) => [ - styles.step, - pressed ? styles.pressed : active ? styles.active : styles.inactive, - ]} - /> - ); -}; - -const size = 30; - -const styles = StyleSheet.create({ - step: { - width: size, - height: size, - margin: layout.spacing / 2, - }, - pressed: { - backgroundColor: `${colors.main}88`, - }, - active: { - backgroundColor: colors.main, - }, - inactive: { - backgroundColor: colors.white, - }, -}); - -export default Step; diff --git a/apps/common-app/src/components/Steps.tsx b/apps/common-app/src/components/Steps.tsx deleted file mode 100644 index ed266467..00000000 --- a/apps/common-app/src/components/Steps.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { FC } from 'react'; -import { View, StyleSheet, Text } from 'react-native'; - -import Step from './Step'; -import { layout } from '../styles'; -import { SoundName } from '../types'; -interface StepsProps { - name: SoundName; - steps: boolean[]; - handleStepClick: (name: SoundName, idx: number) => void; -} - -const Steps: FC = (props) => { - const { name, steps, handleStepClick } = props; - - const handleClick = (idx: number) => { - handleStepClick(name, idx); - }; - - return ( - - {name} - - {steps.map((active, idx) => ( - handleClick(idx)} - /> - ))} - - - ); -}; - -const styles = StyleSheet.create({ - container: { - display: 'flex', - flexDirection: 'row', - width: '100%', - justifyContent: 'space-between', - alignItems: 'center', - }, - steps: { - display: 'flex', - flexDirection: 'row', - alignItems: 'center', - }, - text: { - margin: layout.spacing, - textTransform: 'capitalize', - }, -}); - -export default Steps; diff --git a/apps/common-app/src/components/index.ts b/apps/common-app/src/components/index.ts index 243cd2eb..4cae6427 100644 --- a/apps/common-app/src/components/index.ts +++ b/apps/common-app/src/components/index.ts @@ -1,6 +1,4 @@ export { default as Icon } from './Icon'; -export { default as Step } from './Step'; -export { default as Steps } from './Steps'; export { default as Button } from './Button'; export { default as Slider } from './Slider'; export { default as Spacer } from './Spacer'; diff --git a/apps/common-app/src/examples/DrumMachine/DrumMachine.tsx b/apps/common-app/src/examples/DrumMachine/DrumMachine.tsx index e179a73f..eca80c18 100644 --- a/apps/common-app/src/examples/DrumMachine/DrumMachine.tsx +++ b/apps/common-app/src/examples/DrumMachine/DrumMachine.tsx @@ -6,16 +6,19 @@ import { LayoutChangeEvent, StyleSheet, View } from 'react-native'; import { colors } from '../../styles'; import { Select, Slider, Spacer, Container } from '../../components'; -import { Kick, Clap, HiHat } from '../SharedUtils'; +import Kick from '../../utils/soundEngines/Kick'; +import Clap from '../../utils/soundEngines/Clap'; +import HiHat from '../../utils/soundEngines/HiHat'; -import { InstrumentName, Pattern, type XYWHRect } from './types'; +import { InstrumentName, Pattern, type XYWHRect } from '../../types'; import { size, initialBpm } from './constants'; import NotesHighlight from './NotesHighlight'; import PatternShape from './PatternShape'; import useGestures from './useGestures'; import PlayButton from './PlayButton'; -import usePlayer from './usePlayer'; +import usePlayer from '../../utils/usePlayer'; import presets from './presets'; +import { numBeats } from './constants'; import Grid from './Grid'; const defaultPreset = 'Empty'; @@ -54,6 +57,7 @@ const DrumMachine: React.FC = () => { bpm, patterns, notesPerBeat: 2, + numBeats, setup: setupPlayer, }); diff --git a/apps/common-app/src/examples/DrumMachine/Grid.tsx b/apps/common-app/src/examples/DrumMachine/Grid.tsx index 69108aec..b480d400 100644 --- a/apps/common-app/src/examples/DrumMachine/Grid.tsx +++ b/apps/common-app/src/examples/DrumMachine/Grid.tsx @@ -5,7 +5,7 @@ import { Line, Circle, Paint, vec } from '@shopify/react-native-skia'; import { colors } from '../../styles'; import { numBeats, cPoint, maxSize, buttonRadius } from './constants'; import { getAngle, getPointCX, getPointCY } from './utils'; -import type { Instrument } from './types'; +import type { Instrument } from '../../types'; import instruments from './instruments'; const points = Array(numBeats).fill(0); diff --git a/apps/common-app/src/examples/DrumMachine/NotesHighlight.tsx b/apps/common-app/src/examples/DrumMachine/NotesHighlight.tsx index 960e8116..b6bfd225 100644 --- a/apps/common-app/src/examples/DrumMachine/NotesHighlight.tsx +++ b/apps/common-app/src/examples/DrumMachine/NotesHighlight.tsx @@ -2,7 +2,7 @@ import React, { memo } from 'react'; import { Circle, Paint } from '@shopify/react-native-skia'; import { SharedValue, useDerivedValue } from 'react-native-reanimated'; -import { Instrument, PlayingInstruments } from './types'; +import { Instrument, PlayingInstruments } from '../../types'; import { cPoint, buttonRadius } from './constants'; import { getPointCX, getPointCY } from './utils'; import instruments from './instruments'; diff --git a/apps/common-app/src/examples/DrumMachine/PatternShape.tsx b/apps/common-app/src/examples/DrumMachine/PatternShape.tsx index 2bd1f6c5..5efa9f8b 100644 --- a/apps/common-app/src/examples/DrumMachine/PatternShape.tsx +++ b/apps/common-app/src/examples/DrumMachine/PatternShape.tsx @@ -3,7 +3,7 @@ import { Circle, Paint, Path, vec, Skia } from '@shopify/react-native-skia'; import { getAngle, getPointCX, getPointCY } from './utils'; import instruments from './instruments'; -import type { Pattern } from './types'; +import type { Pattern } from '../../types'; import { buttonRadius, cPoint } from './constants'; interface PatternShapeProps { diff --git a/apps/common-app/src/examples/DrumMachine/PlayButton.tsx b/apps/common-app/src/examples/DrumMachine/PlayButton.tsx index 1bf19923..f9c1e057 100644 --- a/apps/common-app/src/examples/DrumMachine/PlayButton.tsx +++ b/apps/common-app/src/examples/DrumMachine/PlayButton.tsx @@ -8,7 +8,7 @@ import { Pressable, StyleSheet } from 'react-native'; import { Icon } from '../../components'; import { colors } from '../../styles'; -import type { PlayingInstruments, XYWHRect } from './types'; +import type { PlayingInstruments, XYWHRect } from '../../types'; import { size } from './constants'; interface PlayButtonProps { diff --git a/apps/common-app/src/examples/DrumMachine/constants.ts b/apps/common-app/src/examples/DrumMachine/constants.ts index 4e8f6be3..0b329aab 100644 --- a/apps/common-app/src/examples/DrumMachine/constants.ts +++ b/apps/common-app/src/examples/DrumMachine/constants.ts @@ -1,5 +1,5 @@ import { Dimensions } from 'react-native'; -import type { InstrumentName, XYPoint } from './types'; +import type { InstrumentName, XYPoint } from '../../types'; export const screenSize = Dimensions.get('screen'); export const size = Math.min(screenSize.width, screenSize.height); diff --git a/apps/common-app/src/examples/DrumMachine/instruments.ts b/apps/common-app/src/examples/DrumMachine/instruments.ts index f120da39..4d222cde 100644 --- a/apps/common-app/src/examples/DrumMachine/instruments.ts +++ b/apps/common-app/src/examples/DrumMachine/instruments.ts @@ -1,5 +1,5 @@ import { maxSize } from './constants'; -import type { Instrument } from './types'; +import type { Instrument } from '../../types'; export const HiHat: Instrument = { name: 'hi-hat', diff --git a/apps/common-app/src/examples/DrumMachine/presets.ts b/apps/common-app/src/examples/DrumMachine/presets.ts index f93f4808..3f0c42ac 100644 --- a/apps/common-app/src/examples/DrumMachine/presets.ts +++ b/apps/common-app/src/examples/DrumMachine/presets.ts @@ -1,4 +1,4 @@ -import type { Preset } from './types'; +import type { Preset } from '../../types'; import { numBeats } from './constants'; const Empty: Preset = { diff --git a/apps/common-app/src/examples/DrumMachine/types.ts b/apps/common-app/src/examples/DrumMachine/types.ts deleted file mode 100644 index 8b3d695c..00000000 --- a/apps/common-app/src/examples/DrumMachine/types.ts +++ /dev/null @@ -1,39 +0,0 @@ -export type InstrumentName = 'kick' | 'clap' | 'hi-hat'; - -export interface Instrument { - color: string; - radius: number; - name: InstrumentName; -} - -export interface Touché { - cX: number; - cY: number; - stepIdx: number; - patternIdx: number; -} - -export interface Pattern { - instrumentName: InstrumentName; - steps: Array; -} - -export interface Preset { - name: string; - bpm: number; - pattern: Pattern[]; -} - -export interface XYWHRect { - x: number; - y: number; - width: number; - height: number; -} - -export interface XYPoint { - x: number; - y: number; -} - -export type PlayingInstruments = Record; diff --git a/apps/common-app/src/examples/DrumMachine/useGestures.ts b/apps/common-app/src/examples/DrumMachine/useGestures.ts index fe30f0ff..5154f5bd 100644 --- a/apps/common-app/src/examples/DrumMachine/useGestures.ts +++ b/apps/common-app/src/examples/DrumMachine/useGestures.ts @@ -8,7 +8,7 @@ import { import { getAngle, getPointCX, getPointCY, isPointInCircle } from './utils'; import { numBeats, buttonRadius, cPoint } from './constants'; -import type { Touché, XYWHRect } from './types'; +import type { Touché, XYWHRect } from '../../types'; import Instruments from './instruments'; import { useMemo } from 'react'; diff --git a/apps/common-app/src/examples/Metronome/Metronome.tsx b/apps/common-app/src/examples/Metronome/Metronome.tsx index 69aca1da..4613d21f 100644 --- a/apps/common-app/src/examples/Metronome/Metronome.tsx +++ b/apps/common-app/src/examples/Metronome/Metronome.tsx @@ -1,137 +1,78 @@ import { AudioContext } from 'react-native-audio-api'; -import React, { useState, useEffect, useRef, FC } from 'react'; +import React, { useState, useCallback, FC } from 'react'; import { Container, Slider, Spacer, Button } from '../../components'; -import { Scheduler, MetronomeSound } from '../SharedUtils'; -import { Sounds, SoundName } from '../../types'; +import MetronomeSound from '../../utils/soundEngines/MetronomeSound'; +import { InstrumentName, Pattern } from '../../types'; +import { patterns, defaultPattern } from './patterns'; +import usePlayer from '../../utils/usePlayer'; const DOWN_BEAT_FREQUENCY = 1000; const REGULAR_BEAT_FREQUENCY = 500; -const STEPS: Sounds = [ - { name: 'downbeat', steps: [true, false, false, false] }, - { name: 'regularbeat', steps: [false, true, true, true] }, -]; - const INITIAL_BPM = 120; const INITIAL_BEATS_PER_BAR = 4; -const Metronome: FC = () => { - const [bpm, setBpm] = useState(INITIAL_BPM); - const [beatsPerBar, setBeatsPerBar] = useState(INITIAL_BEATS_PER_BAR); - const [isPlaying, setIsPlaying] = useState(false); - - const audioContextRef = useRef(null); - const downbeatSoundRef = useRef(null); - const regularbeatSoundRef = useRef(null); - const schedulerRef = useRef(null); - - const handlePause = () => { - setIsPlaying(false); - schedulerRef.current?.stop(); - }; - - const handlePlayPause = () => { - if (!audioContextRef.current || !schedulerRef.current) { - return; - } - - if (isPlaying) { - handlePause(); - return; - } - - setIsPlaying(true); - schedulerRef.current.start(); - }; - - const handleBpmChange = (newBpm: number) => { - handlePause(); - setBpm(newBpm); - if (schedulerRef.current) { - schedulerRef.current.bpm = newBpm; - } - }; - - const handleBeatsPerBarChange = (newBeatsPerBar: number) => { - handlePause(); - setBeatsPerBar(newBeatsPerBar); - if (schedulerRef.current) { - schedulerRef.current.beatsPerBar = newBeatsPerBar; - const steps = new Array(newBeatsPerBar).fill(false); - steps[0] = true; - - schedulerRef.current.steps = [ - { name: 'downbeat', steps }, - { name: 'regularbeat', steps: steps.map((value) => !value) }, - ]; - } - }; - - const playSound = (name: SoundName, time: number) => { - if (!audioContextRef.current || !schedulerRef.current) { - return; - } - - if (!downbeatSoundRef.current) { - downbeatSoundRef.current = new MetronomeSound( - audioContextRef.current, - DOWN_BEAT_FREQUENCY - ); - } - - if (!regularbeatSoundRef.current) { - regularbeatSoundRef.current = new MetronomeSound( - audioContextRef.current, - REGULAR_BEAT_FREQUENCY - ); - } +function setupPlayer(audioCtx: AudioContext) { + const downbeat = new MetronomeSound(audioCtx, DOWN_BEAT_FREQUENCY); + const regularbeat = new MetronomeSound(audioCtx, REGULAR_BEAT_FREQUENCY); + const playNote = (name: InstrumentName, time: number) => { switch (name) { case 'downbeat': - downbeatSoundRef.current.play(time); + downbeat.play(time); break; case 'regularbeat': - regularbeatSoundRef.current.play(time); - break; - default: + regularbeat.play(time); break; } }; - useEffect(() => { - if (!audioContextRef.current || !schedulerRef.current) { - audioContextRef.current = new AudioContext(); - } + return { playNote }; +} - if (!schedulerRef.current) { - schedulerRef.current = new Scheduler( - INITIAL_BPM, - INITIAL_BEATS_PER_BAR, - audioContextRef.current, - STEPS, - 1, - playSound - ); - } - return () => { - schedulerRef.current?.stop(); - audioContextRef.current?.close(); - }; +const Metronome: FC = () => { + const [bpm, setBpm] = useState(INITIAL_BPM); + const [beatsPerBar, setBeatsPerBar] = useState(INITIAL_BEATS_PER_BAR); + const [pattern, setPattern] = useState([ + ...patterns[defaultPattern], + ]); + + const player = usePlayer({ + bpm, + patterns: pattern, + notesPerBeat: 1, + numBeats: beatsPerBar, + setup: setupPlayer, + }); + + const onBeatsPerBarChange = useCallback((newBeatsPerBar: number) => { + setBeatsPerBar(newBeatsPerBar); + setPattern([...patterns[newBeatsPerBar]]); }, []); + const onPlayPress = useCallback(() => { + if (player.isPlaying) { + player.stop(); + } else { + player.play(); + } + }, [player]); + return ( -