2 Commits

Author SHA1 Message Date
Clément Le Bihan
0db8d49618 nothing important 2024-01-04 15:30:05 +01:00
Clément Le Bihan
4923fc72b2 reactènative-sounds 2023-12-31 17:59:56 +01:00
93 changed files with 147 additions and 116 deletions

View File

@@ -1,12 +1,12 @@
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { Slider, View, IconButton, Icon } from 'native-base'; import { Slider, View, IconButton, Icon } from 'native-base';
import { MaterialCommunityIcons } from '@expo/vector-icons'; import { MaterialCommunityIcons } from '@expo/vector-icons';
import { Audio } from 'expo-av'; // import { Audio } from 'expo-av';
import { VolumeHigh, VolumeSlash } from 'iconsax-react-native'; import { VolumeHigh, VolumeSlash } from 'iconsax-react-native';
import { Translate } from '../i18n/i18n'; import { Translate } from '../i18n/i18n';
export const MetronomeControls = ({ paused = false, bpm }: { paused?: boolean; bpm: number }) => { export const MetronomeControls = ({ paused = false, bpm }: { paused?: boolean; bpm: number }) => {
const audio = useRef<Audio.Sound | null>(null); const audio = useRef<null>(null);
const [enabled, setEnabled] = useState<boolean>(false); const [enabled, setEnabled] = useState<boolean>(false);
const volume = useRef<number>(50); const volume = useRef<number>(50);
@@ -15,12 +15,12 @@ export const MetronomeControls = ({ paused = false, bpm }: { paused?: boolean; b
return; return;
} else if (!audio.current) { } else if (!audio.current) {
// eslint-disable-next-line @typescript-eslint/no-var-requires // eslint-disable-next-line @typescript-eslint/no-var-requires
Audio.Sound.createAsync(require('../assets/metronome.mp3')).then((a) => { // Audio.Sound.createAsync(require('../assets/metronome.mp3')).then((a) => {
audio.current = a.sound; // audio.current = a.sound;
}); // });
} }
return () => { return () => {
audio.current?.unloadAsync(); // audio.current?.unloadAsync();
}; };
}, [enabled]); }, [enabled]);
useEffect(() => { useEffect(() => {
@@ -28,12 +28,12 @@ export const MetronomeControls = ({ paused = false, bpm }: { paused?: boolean; b
const int = setInterval(() => { const int = setInterval(() => {
if (!enabled) return; if (!enabled) return;
if (!audio.current) return; if (!audio.current) return;
audio.current?.playAsync(); // audio.current?.playAsync();
}, 60000 / bpm); }, 60000 / bpm);
return () => clearInterval(int); return () => clearInterval(int);
}, [bpm, paused]); }, [bpm, paused]);
useEffect(() => { useEffect(() => {
audio.current?.setVolumeAsync(volume.current / 100); // audio.current?.setVolumeAsync(volume.current / 100);
}, [volume.current]); }, [volume.current]);
return ( return (
<View flex={1}> <View flex={1}>

View File

@@ -5,9 +5,12 @@ import { useQuery } from '../../Queries';
import Animated, { useSharedValue, withTiming, Easing } from 'react-native-reanimated'; import Animated, { useSharedValue, withTiming, Easing } from 'react-native-reanimated';
import { CursorInfoItem } from '../../models/SongCursorInfos'; import { CursorInfoItem } from '../../models/SongCursorInfos';
import { PianoNotes } from '../../state/SoundPlayerSlice'; import { PianoNotes } from '../../state/SoundPlayerSlice';
import { Audio } from 'expo-av'; // import { Audio } from 'expo-av';
import { SvgContainer } from './SvgContainer'; import { SvgContainer } from './SvgContainer';
import LoadingComponent from '../Loading'; import LoadingComponent from '../Loading';
import Sound from 'react-native-sound';
Sound.setCategory('Playback');
// note we are also using timestamp in a context // note we are also using timestamp in a context
export type ParitionMagicProps = { export type ParitionMagicProps = {
@@ -51,7 +54,7 @@ const PartitionMagic = ({
const [endPartitionReached, setEndPartitionReached] = React.useState(false); const [endPartitionReached, setEndPartitionReached] = React.useState(false);
const [isPartitionSvgLoaded, setIsPartitionSvgLoaded] = React.useState(false); const [isPartitionSvgLoaded, setIsPartitionSvgLoaded] = React.useState(false);
const partitionOffset = useSharedValue(0); const partitionOffset = useSharedValue(0);
const pianoSounds = React.useRef<Record<string, Audio.Sound> | null>(null); const pianoSounds = React.useRef<Record<string, Sound> | null>(null);
const cursorPaddingVertical = 10; const cursorPaddingVertical = 10;
const cursorPaddingHorizontal = 3; const cursorPaddingHorizontal = 3;
@@ -72,21 +75,40 @@ const PartitionMagic = ({
React.useEffect(() => { React.useEffect(() => {
if (!pianoSounds.current) { if (!pianoSounds.current) {
Promise.all( Promise.all(
Object.entries(PianoNotes).map(([midiNumber, noteResource]) => Object.entries(PianoNotes).map(([midiNumber, noteResource]) => {
Audio.Sound.createAsync(noteResource, { // Audio.Sound.createAsync(noteResource, {
volume: 1, // volume: 1,
progressUpdateIntervalMillis: 100, // progressUpdateIntervalMillis: 100,
}).then((sound) => [midiNumber, sound.sound] as const) // }).then((sound) => [midiNumber, sound.sound] as const)
) return new Promise((resolve, reject) => {
const sound = new Sound(noteResource, Sound.MAIN_BUNDLE, (error: any) => {
if (error) {
reject(error);
} else {
resolve([midiNumber, sound] as const);
}
});
});
})
).then((res) => { ).then((res) => {
pianoSounds.current = res.reduce( // pianoSounds.current = res.reduce(
(prev, curr) => ({ ...prev, [curr[0]]: curr[1] }), // (prev, curr) => ({ ...prev, [curr[0]]: curr[1] }),
{} // {}
); // );
pianoSounds.current = {};
(res as [string, Sound][]).forEach((curr) => {
pianoSounds.current![curr[0]] = curr[1];
});
console.log('sound loaded'); console.log('sound loaded');
}); });
} }
}, []); }, [
() => {
pianoSounds?.current?.forEach((sound) => {
sound.release();
});
},
]);
const partitionDims = React.useMemo<[number, number]>(() => { const partitionDims = React.useMemo<[number, number]>(() => {
return [data?.pageWidth ?? 0, data?.pageHeight ?? 1]; return [data?.pageWidth ?? 0, data?.pageHeight ?? 1];
}, [data]); }, [data]);
@@ -122,12 +144,16 @@ const PartitionMagic = ({
cursor.notes.forEach(({ note, duration }) => { cursor.notes.forEach(({ note, duration }) => {
try { try {
const sound = pianoSounds.current![note]!; const sound = pianoSounds.current![note]!;
sound.playAsync().catch(console.error); sound.play((success) => {
if (!success) {
console.log('Sound did not play');
}
});
setTimeout(() => { setTimeout(() => {
sound.stopAsync(); sound.stop();
}, duration - 10); }, duration - 10);
} catch (e) { } catch (e) {
console.log(e); console.log('Error key: ', note, e);
} }
}); });
} }

View File

@@ -50,6 +50,7 @@
"react-native-reanimated": "~3.3.0", "react-native-reanimated": "~3.3.0",
"react-native-safe-area-context": "4.6.3", "react-native-safe-area-context": "4.6.3",
"react-native-screens": "~3.22.0", "react-native-screens": "~3.22.0",
"react-native-sound": "^0.11.2",
"react-native-super-grid": "^5.0.0", "react-native-super-grid": "^5.0.0",
"react-native-svg": "14.0.0", "react-native-svg": "14.0.0",
"react-native-tab-view": "^3.5.2", "react-native-tab-view": "^3.5.2",

View File

@@ -1,5 +1,5 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { Audio } from 'expo-av'; // import { Audio } from 'expo-av';
type MidiNumber = number; type MidiNumber = number;
@@ -7,97 +7,97 @@ type MidiNumber = number;
// Deserve an extra credit for doing this by hand // Deserve an extra credit for doing this by hand
// The value is the value returned by `required`, needed by Expo to load/play the sound // The value is the value returned by `required`, needed by Expo to load/play the sound
export const PianoNotes = { export const PianoNotes = {
33: require('../assets/piano/a0.mp3'), 33: 'a0.mp3',
45: require('../assets/piano/a1.mp3'), 45: 'a1.mp3',
57: require('../assets/piano/a2.mp3'), 57: 'a2.mp3',
69: require('../assets/piano/a3.mp3'), 69: 'a3.mp3',
81: require('../assets/piano/a4.mp3'), 81: 'a4.mp3',
93: require('../assets/piano/a5.mp3'), 93: 'a5.mp3',
105: require('../assets/piano/a6.mp3'), 105: 'a6.mp3',
117: require('../assets/piano/a7.mp3'), 117: 'a7.mp3',
44: require('../assets/piano/ab1.mp3'), 44: 'ab1.mp3',
56: require('../assets/piano/ab2.mp3'), 56: 'ab2.mp3',
68: require('../assets/piano/ab3.mp3'), 68: 'ab3.mp3',
80: require('../assets/piano/ab4.mp3'), 80: 'ab4.mp3',
92: require('../assets/piano/ab5.mp3'), 92: 'ab5.mp3',
104: require('../assets/piano/ab6.mp3'), 104: 'ab6.mp3',
116: require('../assets/piano/ab7.mp3'), 116: 'ab7.mp3',
35: require('../assets/piano/b0.mp3'), 35: 'b0.mp3',
47: require('../assets/piano/b1.mp3'), 47: 'b1.mp3',
59: require('../assets/piano/b2.mp3'), 59: 'b2.mp3',
71: require('../assets/piano/b3.mp3'), 71: 'b3.mp3',
83: require('../assets/piano/b4.mp3'), 83: 'b4.mp3',
95: require('../assets/piano/b5.mp3'), 95: 'b5.mp3',
107: require('../assets/piano/b6.mp3'), 107: 'b6.mp3',
119: require('../assets/piano/b7.mp3'), 119: 'b7.mp3',
34: require('../assets/piano/bb0.mp3'), 34: 'bb0.mp3',
46: require('../assets/piano/bb1.mp3'), 46: 'bb1.mp3',
58: require('../assets/piano/bb2.mp3'), 58: 'bb2.mp3',
70: require('../assets/piano/bb3.mp3'), 70: 'bb3.mp3',
82: require('../assets/piano/bb4.mp3'), 82: 'bb4.mp3',
94: require('../assets/piano/bb5.mp3'), 94: 'bb5.mp3',
106: require('../assets/piano/bb6.mp3'), 106: 'bb6.mp3',
118: require('../assets/piano/bb7.mp3'), 118: 'bb7.mp3',
36: require('../assets/piano/c1.mp3'), 36: 'c1.mp3',
48: require('../assets/piano/c2.mp3'), 48: 'c2.mp3',
60: require('../assets/piano/c3.mp3'), 60: 'c3.mp3',
72: require('../assets/piano/c4.mp3'), 72: 'c4.mp3',
84: require('../assets/piano/c5.mp3'), 84: 'c5.mp3',
96: require('../assets/piano/c6.mp3'), 96: 'c6.mp3',
108: require('../assets/piano/c7.mp3'), 108: 'c7.mp3',
120: require('../assets/piano/c8.mp3'), 120: 'c8.mp3',
38: require('../assets/piano/d1.mp3'), 38: 'd1.mp3',
50: require('../assets/piano/d2.mp3'), 50: 'd2.mp3',
62: require('../assets/piano/d3.mp3'), 62: 'd3.mp3',
74: require('../assets/piano/d4.mp3'), 74: 'd4.mp3',
86: require('../assets/piano/d5.mp3'), 86: 'd5.mp3',
98: require('../assets/piano/d6.mp3'), 98: 'd6.mp3',
110: require('../assets/piano/d7.mp3'), 110: 'd7.mp3',
37: require('../assets/piano/db1.mp3'), 37: 'db1.mp3',
49: require('../assets/piano/db2.mp3'), 49: 'db2.mp3',
61: require('../assets/piano/db3.mp3'), 61: 'db3.mp3',
73: require('../assets/piano/db4.mp3'), 73: 'db4.mp3',
85: require('../assets/piano/db5.mp3'), 85: 'db5.mp3',
97: require('../assets/piano/db6.mp3'), 97: 'db6.mp3',
109: require('../assets/piano/db7.mp3'), 109: 'db7.mp3',
40: require('../assets/piano/e1.mp3'), 40: 'e1.mp3',
52: require('../assets/piano/e2.mp3'), 52: 'e2.mp3',
64: require('../assets/piano/e3.mp3'), 64: 'e3.mp3',
76: require('../assets/piano/e4.mp3'), 76: 'e4.mp3',
88: require('../assets/piano/e5.mp3'), 88: 'e5.mp3',
100: require('../assets/piano/e6.mp3'), 100: 'e6.mp3',
112: require('../assets/piano/e7.mp3'), 112: 'e7.mp3',
39: require('../assets/piano/eb1.mp3'), 39: 'eb1.mp3',
51: require('../assets/piano/eb2.mp3'), 51: 'eb2.mp3',
63: require('../assets/piano/eb3.mp3'), 63: 'eb3.mp3',
75: require('../assets/piano/eb4.mp3'), 75: 'eb4.mp3',
87: require('../assets/piano/eb5.mp3'), 87: 'eb5.mp3',
99: require('../assets/piano/eb6.mp3'), 99: 'eb6.mp3',
111: require('../assets/piano/eb7.mp3'), 111: 'eb7.mp3',
41: require('../assets/piano/f1.mp3'), 41: 'f1.mp3',
53: require('../assets/piano/f2.mp3'), 53: 'f2.mp3',
65: require('../assets/piano/f3.mp3'), 65: 'f3.mp3',
77: require('../assets/piano/f4.mp3'), 77: 'f4.mp3',
89: require('../assets/piano/f5.mp3'), 89: 'f5.mp3',
101: require('../assets/piano/f6.mp3'), 101: 'f6.mp3',
113: require('../assets/piano/f7.mp3'), 113: 'f7.mp3',
43: require('../assets/piano/g1.mp3'), 43: 'g1.mp3',
55: require('../assets/piano/g2.mp3'), 55: 'g2.mp3',
67: require('../assets/piano/g3.mp3'), 67: 'g3.mp3',
79: require('../assets/piano/g4.mp3'), 79: 'g4.mp3',
91: require('../assets/piano/g5.mp3'), 91: 'g5.mp3',
103: require('../assets/piano/g6.mp3'), 103: 'g6.mp3',
115: require('../assets/piano/g7.mp3'), 115: 'g7.mp3',
42: require('../assets/piano/gb1.mp3'), 42: 'gb1.mp3',
54: require('../assets/piano/gb2.mp3'), 54: 'gb2.mp3',
66: require('../assets/piano/gb3.mp3'), 66: 'gb3.mp3',
78: require('../assets/piano/gb4.mp3'), 78: 'gb4.mp3',
90: require('../assets/piano/gb5.mp3'), 90: 'gb5.mp3',
102: require('../assets/piano/gb6.mp3'), 102: 'gb6.mp3',
114: require('../assets/piano/gb7.mp3'), 114: 'gb7.mp3',
} as const; } as const;
export type Sounds = Record<MidiNumber, Audio.Sound>; export type Sounds = Record<MidiNumber, string>;
export const soundPlayerSlice = createSlice({ export const soundPlayerSlice = createSlice({
name: 'soundPlayer', name: 'soundPlayer',
@@ -107,11 +107,10 @@ export const soundPlayerSlice = createSlice({
reducers: { reducers: {
setSounds: (state, action: PayloadAction<Sounds>) => { setSounds: (state, action: PayloadAction<Sounds>) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-expect-error
state.sounds = action.payload; state.sounds = action.payload;
}, },
unsetSounds: (state) => { unsetSounds: (state) => {
Object.entries(state.sounds ?? {}).map((sound) => sound[1].unloadAsync()); // Object.entries(state.sounds ?? {}).map((sound) => sound[1].unloadAsync());
state.sounds = undefined; state.sounds = undefined;
}, },
}, },

View File

@@ -9948,6 +9948,11 @@ react-native-screens@~3.22.0:
react-freeze "^1.0.0" react-freeze "^1.0.0"
warn-once "^0.1.0" warn-once "^0.1.0"
react-native-sound@^0.11.2:
version "0.11.2"
resolved "https://registry.yarnpkg.com/react-native-sound/-/react-native-sound-0.11.2.tgz#e542dc5b9e16ab4b3ac7e6eaddb1fc8d98da9038"
integrity sha512-LmGc8lgOK3qecYMVQpyHvww/C+wgT6sWeMpVbOe4NCRGC2yKd4fo4U0KBUo9PO7AqKESO3I/2GZg1/C0+bwiiA==
react-native-super-grid@^5.0.0: react-native-super-grid@^5.0.0:
version "5.0.0" version "5.0.0"
resolved "https://registry.yarnpkg.com/react-native-super-grid/-/react-native-super-grid-5.0.0.tgz#0cf9cfb86864198a1e6925d26496bd005b319e3e" resolved "https://registry.yarnpkg.com/react-native-super-grid/-/react-native-super-grid-5.0.0.tgz#0cf9cfb86864198a1e6925d26496bd005b319e3e"