stream-chat-react-native-core 9.10.2 → 9.11.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/ai-components/README.md +50 -2
- package/lib/commonjs/ai-components/index.js +24 -0
- package/lib/commonjs/ai-components/index.js.map +1 -1
- package/lib/commonjs/ai-components/message-composer/ComposerView.js +4 -2
- package/lib/commonjs/ai-components/message-composer/ComposerView.js.map +1 -1
- package/lib/commonjs/ai-components/message-composer/SpeechToTextButton.js +4 -1
- package/lib/commonjs/ai-components/message-composer/SpeechToTextButton.js.map +1 -1
- package/lib/commonjs/ai-components/native/AIDictation.js +6 -0
- package/lib/commonjs/ai-components/native/AIDictation.js.map +1 -1
- package/lib/commonjs/ai-components/store/dictation/store.js +4 -2
- package/lib/commonjs/ai-components/store/dictation/store.js.map +1 -1
- package/lib/commonjs/ai-components/store/dictation/useDictationState.js +4 -2
- package/lib/commonjs/ai-components/store/dictation/useDictationState.js.map +1 -1
- package/lib/commonjs/ai-components/store/index.js +22 -0
- package/lib/commonjs/ai-components/store/index.js.map +1 -1
- package/lib/commonjs/ai-components/theme.js +3 -0
- package/lib/commonjs/ai-components/theme.js.map +1 -1
- package/lib/commonjs/ai-components/transcription/useDictation.js +7 -3
- package/lib/commonjs/ai-components/transcription/useDictation.js.map +1 -1
- package/lib/commonjs/ai-components/transcription/useDictationAudioLevel.js +32 -0
- package/lib/commonjs/ai-components/transcription/useDictationAudioLevel.js.map +1 -0
- package/lib/commonjs/ai-components/voice-visualization/VoiceGlow.js +79 -0
- package/lib/commonjs/ai-components/voice-visualization/VoiceGlow.js.map +1 -0
- package/lib/commonjs/ai-components/voice-visualization/VoiceVisualizationView.js +147 -0
- package/lib/commonjs/ai-components/voice-visualization/VoiceVisualizationView.js.map +1 -0
- package/lib/commonjs/ai-components/voice-visualization/VoiceWave.js +58 -0
- package/lib/commonjs/ai-components/voice-visualization/VoiceWave.js.map +1 -0
- package/lib/commonjs/ai-components/voice-visualization/bounce.js +60 -0
- package/lib/commonjs/ai-components/voice-visualization/bounce.js.map +1 -0
- package/lib/commonjs/ai-components/voice-visualization/driver.js +208 -0
- package/lib/commonjs/ai-components/voice-visualization/driver.js.map +1 -0
- package/lib/commonjs/ai-components/voice-visualization/glowShader.js +218 -0
- package/lib/commonjs/ai-components/voice-visualization/glowShader.js.map +1 -0
- package/lib/commonjs/ai-components/voice-visualization/index.js +18 -0
- package/lib/commonjs/ai-components/voice-visualization/index.js.map +1 -0
- package/lib/commonjs/ai-components/voice-visualization/math.js +70 -0
- package/lib/commonjs/ai-components/voice-visualization/math.js.map +1 -0
- package/lib/commonjs/ai-components/voice-visualization/palette.js +47 -0
- package/lib/commonjs/ai-components/voice-visualization/palette.js.map +1 -0
- package/lib/commonjs/ai-components/voice-visualization/simplex.js +77 -0
- package/lib/commonjs/ai-components/voice-visualization/simplex.js.map +1 -0
- package/lib/commonjs/ai-components/voice-visualization/styles.js +33 -0
- package/lib/commonjs/ai-components/voice-visualization/styles.js.map +1 -0
- package/lib/commonjs/ai-components/voice-visualization/types.js +4 -0
- package/lib/commonjs/ai-components/voice-visualization/types.js.map +1 -0
- package/lib/commonjs/ai-components/voice-visualization/useReduceMotion.js +28 -0
- package/lib/commonjs/ai-components/voice-visualization/useReduceMotion.js.map +1 -0
- package/lib/commonjs/ai-components/voice-visualization/useVoiceVisualization.js +129 -0
- package/lib/commonjs/ai-components/voice-visualization/useVoiceVisualization.js.map +1 -0
- package/lib/commonjs/ai-components/voice-visualization/waveGeometry.js +124 -0
- package/lib/commonjs/ai-components/voice-visualization/waveGeometry.js.map +1 -0
- package/lib/commonjs/version.json +1 -1
- package/lib/module/ai-components/README.md +50 -2
- package/lib/module/ai-components/index.js +24 -0
- package/lib/module/ai-components/index.js.map +1 -1
- package/lib/module/ai-components/message-composer/ComposerView.js +4 -2
- package/lib/module/ai-components/message-composer/ComposerView.js.map +1 -1
- package/lib/module/ai-components/message-composer/SpeechToTextButton.js +4 -1
- package/lib/module/ai-components/message-composer/SpeechToTextButton.js.map +1 -1
- package/lib/module/ai-components/native/AIDictation.js +6 -0
- package/lib/module/ai-components/native/AIDictation.js.map +1 -1
- package/lib/module/ai-components/store/dictation/store.js +4 -2
- package/lib/module/ai-components/store/dictation/store.js.map +1 -1
- package/lib/module/ai-components/store/dictation/useDictationState.js +4 -2
- package/lib/module/ai-components/store/dictation/useDictationState.js.map +1 -1
- package/lib/module/ai-components/store/index.js +22 -0
- package/lib/module/ai-components/store/index.js.map +1 -1
- package/lib/module/ai-components/theme.js +3 -0
- package/lib/module/ai-components/theme.js.map +1 -1
- package/lib/module/ai-components/transcription/useDictation.js +7 -3
- package/lib/module/ai-components/transcription/useDictation.js.map +1 -1
- package/lib/module/ai-components/transcription/useDictationAudioLevel.js +32 -0
- package/lib/module/ai-components/transcription/useDictationAudioLevel.js.map +1 -0
- package/lib/module/ai-components/voice-visualization/VoiceGlow.js +79 -0
- package/lib/module/ai-components/voice-visualization/VoiceGlow.js.map +1 -0
- package/lib/module/ai-components/voice-visualization/VoiceVisualizationView.js +147 -0
- package/lib/module/ai-components/voice-visualization/VoiceVisualizationView.js.map +1 -0
- package/lib/module/ai-components/voice-visualization/VoiceWave.js +58 -0
- package/lib/module/ai-components/voice-visualization/VoiceWave.js.map +1 -0
- package/lib/module/ai-components/voice-visualization/bounce.js +60 -0
- package/lib/module/ai-components/voice-visualization/bounce.js.map +1 -0
- package/lib/module/ai-components/voice-visualization/driver.js +208 -0
- package/lib/module/ai-components/voice-visualization/driver.js.map +1 -0
- package/lib/module/ai-components/voice-visualization/glowShader.js +218 -0
- package/lib/module/ai-components/voice-visualization/glowShader.js.map +1 -0
- package/lib/module/ai-components/voice-visualization/index.js +18 -0
- package/lib/module/ai-components/voice-visualization/index.js.map +1 -0
- package/lib/module/ai-components/voice-visualization/math.js +70 -0
- package/lib/module/ai-components/voice-visualization/math.js.map +1 -0
- package/lib/module/ai-components/voice-visualization/palette.js +47 -0
- package/lib/module/ai-components/voice-visualization/palette.js.map +1 -0
- package/lib/module/ai-components/voice-visualization/simplex.js +77 -0
- package/lib/module/ai-components/voice-visualization/simplex.js.map +1 -0
- package/lib/module/ai-components/voice-visualization/styles.js +33 -0
- package/lib/module/ai-components/voice-visualization/styles.js.map +1 -0
- package/lib/module/ai-components/voice-visualization/types.js +4 -0
- package/lib/module/ai-components/voice-visualization/types.js.map +1 -0
- package/lib/module/ai-components/voice-visualization/useReduceMotion.js +28 -0
- package/lib/module/ai-components/voice-visualization/useReduceMotion.js.map +1 -0
- package/lib/module/ai-components/voice-visualization/useVoiceVisualization.js +129 -0
- package/lib/module/ai-components/voice-visualization/useVoiceVisualization.js.map +1 -0
- package/lib/module/ai-components/voice-visualization/waveGeometry.js +124 -0
- package/lib/module/ai-components/voice-visualization/waveGeometry.js.map +1 -0
- package/lib/module/version.json +1 -1
- package/lib/typescript/ai-components/index.d.ts +3 -1
- package/lib/typescript/ai-components/index.d.ts.map +1 -1
- package/lib/typescript/ai-components/message-composer/ComposerView.d.ts.map +1 -1
- package/lib/typescript/ai-components/native/AIDictation.d.ts +6 -0
- package/lib/typescript/ai-components/native/AIDictation.d.ts.map +1 -1
- package/lib/typescript/ai-components/store/dictation/store.d.ts +7 -0
- package/lib/typescript/ai-components/store/dictation/store.d.ts.map +1 -1
- package/lib/typescript/ai-components/store/dictation/useDictationState.d.ts +1 -0
- package/lib/typescript/ai-components/store/dictation/useDictationState.d.ts.map +1 -1
- package/lib/typescript/ai-components/store/index.d.ts +2 -0
- package/lib/typescript/ai-components/store/index.d.ts.map +1 -1
- package/lib/typescript/ai-components/theme.d.ts +13 -0
- package/lib/typescript/ai-components/theme.d.ts.map +1 -1
- package/lib/typescript/ai-components/transcription/useDictation.d.ts +9 -1
- package/lib/typescript/ai-components/transcription/useDictation.d.ts.map +1 -1
- package/lib/typescript/ai-components/transcription/useDictationAudioLevel.d.ts +11 -0
- package/lib/typescript/ai-components/transcription/useDictationAudioLevel.d.ts.map +1 -0
- package/lib/typescript/ai-components/voice-visualization/VoiceGlow.d.ts +13 -0
- package/lib/typescript/ai-components/voice-visualization/VoiceGlow.d.ts.map +1 -0
- package/lib/typescript/ai-components/voice-visualization/VoiceVisualizationView.d.ts +26 -0
- package/lib/typescript/ai-components/voice-visualization/VoiceVisualizationView.d.ts.map +1 -0
- package/lib/typescript/ai-components/voice-visualization/VoiceWave.d.ts +13 -0
- package/lib/typescript/ai-components/voice-visualization/VoiceWave.d.ts.map +1 -0
- package/lib/typescript/ai-components/voice-visualization/bounce.d.ts +20 -0
- package/lib/typescript/ai-components/voice-visualization/bounce.d.ts.map +1 -0
- package/lib/typescript/ai-components/voice-visualization/driver.d.ts +96 -0
- package/lib/typescript/ai-components/voice-visualization/driver.d.ts.map +1 -0
- package/lib/typescript/ai-components/voice-visualization/glowShader.d.ts +7 -0
- package/lib/typescript/ai-components/voice-visualization/glowShader.d.ts.map +1 -0
- package/lib/typescript/ai-components/voice-visualization/index.d.ts +4 -0
- package/lib/typescript/ai-components/voice-visualization/index.d.ts.map +1 -0
- package/lib/typescript/ai-components/voice-visualization/math.d.ts +26 -0
- package/lib/typescript/ai-components/voice-visualization/math.d.ts.map +1 -0
- package/lib/typescript/ai-components/voice-visualization/palette.d.ts +18 -0
- package/lib/typescript/ai-components/voice-visualization/palette.d.ts.map +1 -0
- package/lib/typescript/ai-components/voice-visualization/simplex.d.ts +11 -0
- package/lib/typescript/ai-components/voice-visualization/simplex.d.ts.map +1 -0
- package/lib/typescript/ai-components/voice-visualization/styles.d.ts +24 -0
- package/lib/typescript/ai-components/voice-visualization/styles.d.ts.map +1 -0
- package/lib/typescript/ai-components/voice-visualization/types.d.ts +45 -0
- package/lib/typescript/ai-components/voice-visualization/types.d.ts.map +1 -0
- package/lib/typescript/ai-components/voice-visualization/useReduceMotion.d.ts +10 -0
- package/lib/typescript/ai-components/voice-visualization/useReduceMotion.d.ts.map +1 -0
- package/lib/typescript/ai-components/voice-visualization/useVoiceVisualization.d.ts +18 -0
- package/lib/typescript/ai-components/voice-visualization/useVoiceVisualization.d.ts.map +1 -0
- package/lib/typescript/ai-components/voice-visualization/waveGeometry.d.ts +25 -0
- package/lib/typescript/ai-components/voice-visualization/waveGeometry.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/ai-components/README.md +50 -2
- package/src/ai-components/__tests__/VoiceVisualization.test.tsx +208 -0
- package/src/ai-components/__tests__/dictation.test.tsx +58 -1
- package/src/ai-components/__tests__/voiceDriver.test.ts +317 -0
- package/src/ai-components/__tests__/voicePalette.test.ts +124 -0
- package/src/ai-components/index.ts +3 -1
- package/src/ai-components/message-composer/ComposerView.tsx +6 -3
- package/src/ai-components/message-composer/SpeechToTextButton.tsx +5 -2
- package/src/ai-components/native/AIDictation.ts +11 -0
- package/src/ai-components/store/dictation/store.ts +9 -0
- package/src/ai-components/store/dictation/useDictationState.ts +2 -1
- package/src/ai-components/store/index.ts +2 -0
- package/src/ai-components/theme.ts +16 -0
- package/src/ai-components/transcription/useDictation.ts +16 -3
- package/src/ai-components/transcription/useDictationAudioLevel.ts +38 -0
- package/src/ai-components/voice-visualization/VoiceGlow.tsx +79 -0
- package/src/ai-components/voice-visualization/VoiceVisualizationView.tsx +179 -0
- package/src/ai-components/voice-visualization/VoiceWave.tsx +67 -0
- package/src/ai-components/voice-visualization/bounce.ts +61 -0
- package/src/ai-components/voice-visualization/driver.ts +240 -0
- package/src/ai-components/voice-visualization/glowShader.ts +227 -0
- package/src/ai-components/voice-visualization/index.ts +3 -0
- package/src/ai-components/voice-visualization/math.ts +73 -0
- package/src/ai-components/voice-visualization/palette.ts +59 -0
- package/src/ai-components/voice-visualization/simplex.ts +92 -0
- package/src/ai-components/voice-visualization/styles.ts +42 -0
- package/src/ai-components/voice-visualization/types.ts +44 -0
- package/src/ai-components/voice-visualization/useReduceMotion.ts +34 -0
- package/src/ai-components/voice-visualization/useVoiceVisualization.ts +153 -0
- package/src/ai-components/voice-visualization/waveGeometry.ts +137 -0
- package/src/version.json +1 -1
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import { useDerivedValue } from 'react-native-reanimated';
|
|
4
|
+
|
|
5
|
+
import { Canvas, LinearGradient, Path, vec } from '@shopify/react-native-skia';
|
|
6
|
+
|
|
7
|
+
import { WAVE_STYLE } from './styles';
|
|
8
|
+
import type { VoiceVisualizationProps } from './types';
|
|
9
|
+
import type { VoiceVisualization } from './useVoiceVisualization';
|
|
10
|
+
import { type VoiceCanvasSize, VoiceVisualizationView } from './VoiceVisualizationView';
|
|
11
|
+
import {
|
|
12
|
+
WAVE_END,
|
|
13
|
+
WAVE_POSITIONS,
|
|
14
|
+
WAVE_START,
|
|
15
|
+
waveColors,
|
|
16
|
+
wavePath,
|
|
17
|
+
waveStrokeWidth,
|
|
18
|
+
} from './waveGeometry';
|
|
19
|
+
|
|
20
|
+
export type VoiceWaveProps = VoiceVisualizationProps;
|
|
21
|
+
|
|
22
|
+
const WaveCanvas = ({
|
|
23
|
+
size: { height, pixelRatio, width },
|
|
24
|
+
visualization: { dark, driver },
|
|
25
|
+
}: {
|
|
26
|
+
size: VoiceCanvasSize;
|
|
27
|
+
visualization: VoiceVisualization;
|
|
28
|
+
}) => {
|
|
29
|
+
const path = useDerivedValue(() => wavePath(driver.value, width, height));
|
|
30
|
+
const colors = useDerivedValue(() => waveColors(driver.value, dark.value));
|
|
31
|
+
const m = Math.min(width, height);
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<Canvas style={{ height, width }}>
|
|
35
|
+
<Path
|
|
36
|
+
path={path}
|
|
37
|
+
strokeCap='round'
|
|
38
|
+
strokeJoin='round'
|
|
39
|
+
strokeWidth={waveStrokeWidth(width, height, pixelRatio)}
|
|
40
|
+
style='stroke'
|
|
41
|
+
>
|
|
42
|
+
<LinearGradient
|
|
43
|
+
colors={colors}
|
|
44
|
+
end={vec(width / 2 + WAVE_END * m, 0)}
|
|
45
|
+
positions={WAVE_POSITIONS}
|
|
46
|
+
start={vec(width / 2 + WAVE_START * m, 0)}
|
|
47
|
+
/>
|
|
48
|
+
</Path>
|
|
49
|
+
</Canvas>
|
|
50
|
+
);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const renderWave = (visualization: VoiceVisualization, size: VoiceCanvasSize) => (
|
|
54
|
+
<WaveCanvas size={size} visualization={visualization} />
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* A voice agent as a single string, flat at its ends, carrying a wave packet that erupts with
|
|
59
|
+
* speech while its colors flow along it. Tapping the string ripples it from the touch, and so does
|
|
60
|
+
* a hovering mouse or pen.
|
|
61
|
+
*
|
|
62
|
+
* It has no size of its own and draws in the middle of the view it is given, about 80% as wide as
|
|
63
|
+
* its shorter side, so give it a square or a wide box.
|
|
64
|
+
*/
|
|
65
|
+
export const VoiceWave = (props: VoiceWaveProps) => (
|
|
66
|
+
<VoiceVisualizationView {...props} renderCanvas={renderWave} visualStyle={WAVE_STYLE} />
|
|
67
|
+
);
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The bounce of a tap: a dip to 93% and an overshoot to 103% before settling, over 340 ms.
|
|
3
|
+
*
|
|
4
|
+
* These are authored keyframes rather than a spring: the ease-out curve times the whole run and
|
|
5
|
+
* the keyframes are linear between their stops.
|
|
6
|
+
*/
|
|
7
|
+
export const BOUNCE_DURATION = 0.34;
|
|
8
|
+
|
|
9
|
+
const OFFSETS = [0, 0.3, 0.62, 1];
|
|
10
|
+
const SCALES = [1, 0.93, 1.03, 1];
|
|
11
|
+
|
|
12
|
+
/** How long ago the last bounce started, in seconds; infinite before the first. */
|
|
13
|
+
export type BounceState = { age: number };
|
|
14
|
+
|
|
15
|
+
export const createBounce = (): BounceState => ({ age: Number.POSITIVE_INFINITY });
|
|
16
|
+
|
|
17
|
+
export const startBounce = (bounce: BounceState) => {
|
|
18
|
+
'worklet';
|
|
19
|
+
bounce.age = 0;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const advanceBounce = (bounce: BounceState, seconds: number) => {
|
|
23
|
+
'worklet';
|
|
24
|
+
bounce.age += seconds;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/** CSS `ease-out`, `cubic-bezier(0, 0, 0.58, 1)`. */
|
|
28
|
+
export const easeOut = (x: number) => {
|
|
29
|
+
'worklet';
|
|
30
|
+
const bx = 3 * 0.58;
|
|
31
|
+
const ax = 1 - bx;
|
|
32
|
+
const by = 3;
|
|
33
|
+
const ay = 1 - by;
|
|
34
|
+
let low = 0;
|
|
35
|
+
let high = 1;
|
|
36
|
+
for (let i = 0; i < 24; i++) {
|
|
37
|
+
const middle = (low + high) / 2;
|
|
38
|
+
if ((ax * middle + bx) * middle * middle < x) low = middle;
|
|
39
|
+
else high = middle;
|
|
40
|
+
}
|
|
41
|
+
const t = (low + high) / 2;
|
|
42
|
+
return (ay * t + by) * t * t;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/** The keyframed scale at `progress`, from 0 to 1. */
|
|
46
|
+
export const scaleAt = (progress: number) => {
|
|
47
|
+
'worklet';
|
|
48
|
+
for (let i = 1; i < OFFSETS.length; i++) {
|
|
49
|
+
if (progress <= OFFSETS[i]) {
|
|
50
|
+
const fraction = (progress - OFFSETS[i - 1]) / (OFFSETS[i] - OFFSETS[i - 1]);
|
|
51
|
+
return SCALES[i - 1] + (SCALES[i] - SCALES[i - 1]) * fraction;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return 1;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const bounceScale = (bounce: BounceState) => {
|
|
58
|
+
'worklet';
|
|
59
|
+
if (bounce.age >= BOUNCE_DURATION) return 1;
|
|
60
|
+
return scaleAt(easeOut(Math.max(bounce.age, 0) / BOUNCE_DURATION));
|
|
61
|
+
};
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { advanceBounce, type BounceState, bounceScale, createBounce, startBounce } from './bounce';
|
|
2
|
+
import { MAXIMUM_COLORS } from './palette';
|
|
3
|
+
import type { Hsv, VoiceAgentState } from './types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The share of the way the live level moves each frame at 60 Hz, toward a louder or a quieter
|
|
7
|
+
* level.
|
|
8
|
+
*/
|
|
9
|
+
export type LevelResponse = { rise: number; fall: number };
|
|
10
|
+
|
|
11
|
+
export const LEVEL_RESPONSE = {
|
|
12
|
+
/** Follows the level closely, both ways. The glow's. */
|
|
13
|
+
steady: { fall: 0.35, rise: 0.35 },
|
|
14
|
+
/**
|
|
15
|
+
* Follows the level like a VU meter: about 0.1 s up to a louder reading, about 0.3 s down from
|
|
16
|
+
* it, so a meter's per-syllable jumps swell with the phrase instead. The wave's.
|
|
17
|
+
*/
|
|
18
|
+
smooth: { fall: 0.05, rise: 0.16 },
|
|
19
|
+
} satisfies Record<string, LevelResponse>;
|
|
20
|
+
|
|
21
|
+
export type VoiceDriverInput = {
|
|
22
|
+
state: VoiceAgentState;
|
|
23
|
+
/** One to five colors. */
|
|
24
|
+
colors: Hsv[];
|
|
25
|
+
/** From 0 to 2. */
|
|
26
|
+
expressivity: number;
|
|
27
|
+
/** From 0 to 1, or null when no live audio drives the motion. */
|
|
28
|
+
audioLevel: number | null;
|
|
29
|
+
reduceMotion: boolean;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The motion of a voice visualization: where it is in time, how it moves, and its colors. A plain
|
|
34
|
+
* object rather than a class, so it can live in a shared value and be advanced on the UI thread.
|
|
35
|
+
*/
|
|
36
|
+
export type VoiceDriverState = {
|
|
37
|
+
speed: number;
|
|
38
|
+
bright: number;
|
|
39
|
+
sat: number;
|
|
40
|
+
load: number;
|
|
41
|
+
flow: number;
|
|
42
|
+
baseReact: number;
|
|
43
|
+
expressivity: number;
|
|
44
|
+
time: number;
|
|
45
|
+
flowSpin: number;
|
|
46
|
+
orbSpin: number;
|
|
47
|
+
count: number;
|
|
48
|
+
tapX: number;
|
|
49
|
+
tapY: number;
|
|
50
|
+
tapAge: number;
|
|
51
|
+
hoverX: number;
|
|
52
|
+
hoverY: number;
|
|
53
|
+
hoverAmount: number;
|
|
54
|
+
isHovering: boolean;
|
|
55
|
+
mic: number;
|
|
56
|
+
voice: number;
|
|
57
|
+
/** The colors drawn, eased in place toward the input's color slots. */
|
|
58
|
+
colors: Hsv[];
|
|
59
|
+
bounce: BounceState;
|
|
60
|
+
levelResponse: LevelResponse;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* How a state moves. `load`, `flow` and `react` each select a motion pattern, one per state, so
|
|
65
|
+
* easing between states crossfades the patterns.
|
|
66
|
+
*/
|
|
67
|
+
type VoiceMotion = {
|
|
68
|
+
bright: number;
|
|
69
|
+
flow: number;
|
|
70
|
+
load: number;
|
|
71
|
+
react: number;
|
|
72
|
+
sat: number;
|
|
73
|
+
speed: number;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const MOTIONS: Record<VoiceAgentState, VoiceMotion> = {
|
|
77
|
+
connecting: { bright: 1, flow: 0, load: 1, react: 0, sat: 1, speed: 1 },
|
|
78
|
+
idle: { bright: 1, flow: 0, load: 0, react: 0.12, sat: 1, speed: 0.4 },
|
|
79
|
+
listening: { bright: 1, flow: 0, load: 0, react: 0.45, sat: 1, speed: 0.9 },
|
|
80
|
+
speaking: { bright: 1, flow: 0, load: 0, react: 1, sat: 1, speed: 1.25 },
|
|
81
|
+
thinking: { bright: 1, flow: 1, load: 0, react: 0, sat: 1, speed: 1.25 },
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export const NO_TAP = 100;
|
|
85
|
+
const MAX_FRAME = 0.05;
|
|
86
|
+
/** How fast time runs under reduced motion. */
|
|
87
|
+
export const REDUCED = 0.07;
|
|
88
|
+
const DRIVERS = 0.08;
|
|
89
|
+
const GENTLE = 0.12;
|
|
90
|
+
const VOICE = 0.1;
|
|
91
|
+
|
|
92
|
+
/** Expressivity scales amplitude and speed, each with a floor so the motion breathes at 0. */
|
|
93
|
+
export const scales = (expressivity: number) => {
|
|
94
|
+
'worklet';
|
|
95
|
+
return { amplitude: 0.15 + 0.85 * expressivity, speed: 0.6 + 0.4 * expressivity };
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* The share of the way a value moves in `dt` seconds when it moves `perFrame` of the way each
|
|
100
|
+
* frame at 60 Hz.
|
|
101
|
+
*/
|
|
102
|
+
export const ease = (perFrame: number, dt: number) => {
|
|
103
|
+
'worklet';
|
|
104
|
+
return 1 - Math.pow(1 - perFrame, dt * 60);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const rem = (x: number, m: number) => {
|
|
108
|
+
'worklet';
|
|
109
|
+
return ((x % m) + m) % m;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/** Eases an HSV color in place, its hue taking the shorter way round the wheel. */
|
|
113
|
+
const easeHsv = (color: Hsv, target: Hsv, fraction: number) => {
|
|
114
|
+
'worklet';
|
|
115
|
+
const hueStep = (rem(target.hue - color.hue + 1.5, 1) - 0.5) * fraction;
|
|
116
|
+
color.hue = rem(color.hue + hueStep + 1, 1);
|
|
117
|
+
color.saturation += (target.saturation - color.saturation) * fraction;
|
|
118
|
+
color.value += (target.value - color.value) * fraction;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
/** The five color slots the renderers read; past the palette they repeat its first color. */
|
|
122
|
+
export const slots = (colors: Hsv[]): Hsv[] => {
|
|
123
|
+
'worklet';
|
|
124
|
+
const first = colors[0] ?? { hue: 0, saturation: 0, value: 0 };
|
|
125
|
+
const result: Hsv[] = [];
|
|
126
|
+
for (let i = 0; i < MAXIMUM_COLORS; i++) result.push(colors[i] ?? first);
|
|
127
|
+
return result;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
/** A driver settled on `input`, so the first frame shows it rather than easing into it. */
|
|
131
|
+
export const createDriverState = (
|
|
132
|
+
input: VoiceDriverInput,
|
|
133
|
+
levelResponse: LevelResponse = LEVEL_RESPONSE.steady,
|
|
134
|
+
): VoiceDriverState => {
|
|
135
|
+
const m = MOTIONS[input.state];
|
|
136
|
+
const s = scales(input.expressivity);
|
|
137
|
+
return {
|
|
138
|
+
baseReact: m.react * s.amplitude,
|
|
139
|
+
bounce: createBounce(),
|
|
140
|
+
bright: m.bright,
|
|
141
|
+
// Copies, as they are eased in place and the palette belongs to the caller.
|
|
142
|
+
colors: slots(input.colors).map((color) => ({ ...color })),
|
|
143
|
+
count: input.colors.length,
|
|
144
|
+
expressivity: input.expressivity,
|
|
145
|
+
flow: m.flow * s.amplitude,
|
|
146
|
+
flowSpin: 0,
|
|
147
|
+
hoverAmount: 0,
|
|
148
|
+
hoverX: 0,
|
|
149
|
+
hoverY: 0,
|
|
150
|
+
isHovering: false,
|
|
151
|
+
levelResponse,
|
|
152
|
+
load: m.load * s.amplitude,
|
|
153
|
+
mic: 0,
|
|
154
|
+
orbSpin: 0,
|
|
155
|
+
sat: m.sat,
|
|
156
|
+
speed: m.speed * s.speed,
|
|
157
|
+
tapAge: NO_TAP,
|
|
158
|
+
tapX: 0,
|
|
159
|
+
tapY: 0,
|
|
160
|
+
time: 0,
|
|
161
|
+
voice: 0,
|
|
162
|
+
};
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
/** Live audio lifts the motion to its level, whatever the state. */
|
|
166
|
+
export const driverReact = (d: VoiceDriverState) => {
|
|
167
|
+
'worklet';
|
|
168
|
+
return d.baseReact + (Math.max(d.baseReact, d.mic) - d.baseReact) * d.voice;
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
/** The scale of a tap's bounce. */
|
|
172
|
+
export const driverScale = (d: VoiceDriverState) => {
|
|
173
|
+
'worklet';
|
|
174
|
+
return bounceScale(d.bounce);
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
/** A tap at (x, y), centred and normalized to the shorter side with y up. */
|
|
178
|
+
export const tapDriver = (d: VoiceDriverState, input: VoiceDriverInput, x: number, y: number) => {
|
|
179
|
+
'worklet';
|
|
180
|
+
if (input.reduceMotion) return;
|
|
181
|
+
d.tapX = x;
|
|
182
|
+
d.tapY = y;
|
|
183
|
+
d.tapAge = 0;
|
|
184
|
+
startBounce(d.bounce);
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
/** Where a pointer hovers, or null once it has left. */
|
|
188
|
+
export const hoverDriver = (
|
|
189
|
+
d: VoiceDriverState,
|
|
190
|
+
input: VoiceDriverInput,
|
|
191
|
+
x: number | null,
|
|
192
|
+
y: number | null,
|
|
193
|
+
) => {
|
|
194
|
+
'worklet';
|
|
195
|
+
if (x === null || y === null || input.reduceMotion) {
|
|
196
|
+
d.isHovering = false;
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
d.hoverX = x;
|
|
200
|
+
d.hoverY = y;
|
|
201
|
+
d.isHovering = true;
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Advances the motion by `seconds`, easing each value toward the input's motion by a fixed share
|
|
206
|
+
* per frame at 60 Hz, converted to time, so it moves the same at any refresh rate.
|
|
207
|
+
*/
|
|
208
|
+
export const advanceDriver = (d: VoiceDriverState, input: VoiceDriverInput, seconds: number) => {
|
|
209
|
+
'worklet';
|
|
210
|
+
// Clamped so a frame after a stall cannot leap the motion.
|
|
211
|
+
const dt = Math.min(Math.max(seconds, 0), MAX_FRAME);
|
|
212
|
+
if (input.reduceMotion) d.isHovering = false;
|
|
213
|
+
const m = MOTIONS[input.state];
|
|
214
|
+
const s = scales(input.expressivity);
|
|
215
|
+
const drivers = ease(DRIVERS, dt);
|
|
216
|
+
d.speed += (m.speed * s.speed - d.speed) * drivers;
|
|
217
|
+
d.bright += (m.bright - d.bright) * drivers;
|
|
218
|
+
d.sat += (m.sat - d.sat) * drivers;
|
|
219
|
+
d.load += (m.load * s.amplitude - d.load) * drivers;
|
|
220
|
+
d.flow += (m.flow * s.amplitude - d.flow) * drivers;
|
|
221
|
+
d.baseReact += (m.react * s.amplitude - d.baseReact) * drivers;
|
|
222
|
+
d.expressivity += (input.expressivity - d.expressivity) * drivers;
|
|
223
|
+
// Integrated rather than computed as time times a weight, so a change of state eases the turns
|
|
224
|
+
// instead of jumping their angles.
|
|
225
|
+
const step = dt * (input.reduceMotion ? REDUCED : 1) * d.speed;
|
|
226
|
+
d.time += step;
|
|
227
|
+
d.flowSpin += step * d.flow;
|
|
228
|
+
d.orbSpin += step * (1 + 0.5 * d.flow + 0.1 * d.baseReact);
|
|
229
|
+
d.tapAge += dt;
|
|
230
|
+
advanceBounce(d.bounce, dt);
|
|
231
|
+
const gentle = ease(GENTLE, dt);
|
|
232
|
+
d.hoverAmount += ((d.isHovering ? 1 : 0) - d.hoverAmount) * gentle;
|
|
233
|
+
d.voice += ((input.audioLevel === null ? 0 : 1) - d.voice) * ease(VOICE, dt);
|
|
234
|
+
const level = input.audioLevel ?? 0;
|
|
235
|
+
const response = level > d.mic ? d.levelResponse.rise : d.levelResponse.fall;
|
|
236
|
+
d.mic += (level - d.mic) * ease(response, dt);
|
|
237
|
+
const targets = slots(input.colors);
|
|
238
|
+
for (let i = 0; i < d.colors.length; i++) easeHsv(d.colors[i], targets[i], gentle);
|
|
239
|
+
d.count += (input.colors.length - d.count) * gentle;
|
|
240
|
+
};
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { Skia, type SkRuntimeEffect } from '@shopify/react-native-skia';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The glow's shader, SkSL: the AGSL glow of stream-chat-android (itself ported from the iOS Metal
|
|
5
|
+
* glow) as is, since AGSL is SkSL, so the platforms look alike. Every pixel is computed on the
|
|
6
|
+
* GPU: color masses warped by simplex noise, concentric waves, ripples from a tap or a hovering
|
|
7
|
+
* pointer, light and a halo. Pixels past the halo return at once, so only the glow itself costs.
|
|
8
|
+
*/
|
|
9
|
+
const SOURCE = `
|
|
10
|
+
uniform float2 resolution;
|
|
11
|
+
uniform float4 col0;
|
|
12
|
+
uniform float4 col1;
|
|
13
|
+
uniform float4 col2;
|
|
14
|
+
uniform float4 col3;
|
|
15
|
+
uniform float4 col4;
|
|
16
|
+
uniform float time;
|
|
17
|
+
uniform float count;
|
|
18
|
+
uniform float bright;
|
|
19
|
+
uniform float sat;
|
|
20
|
+
uniform float load;
|
|
21
|
+
uniform float flow;
|
|
22
|
+
uniform float flowSpin;
|
|
23
|
+
uniform float orbSpin;
|
|
24
|
+
uniform float react;
|
|
25
|
+
uniform float dark;
|
|
26
|
+
uniform float mic;
|
|
27
|
+
uniform float voice;
|
|
28
|
+
uniform float scale;
|
|
29
|
+
uniform float2 tap;
|
|
30
|
+
uniform float tapTime;
|
|
31
|
+
uniform float2 hover;
|
|
32
|
+
uniform float hoverAmt;
|
|
33
|
+
|
|
34
|
+
float sq(float x) { return x * x; }
|
|
35
|
+
float ss(float e0, float e1, float x) {
|
|
36
|
+
float t = clamp((x - e0) / (e1 - e0), 0.0, 1.0);
|
|
37
|
+
return t * t * (3.0 - 2.0 * t);
|
|
38
|
+
}
|
|
39
|
+
float3 hsv2rgb(float3 c) {
|
|
40
|
+
float4 K = float4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
|
|
41
|
+
float3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
|
|
42
|
+
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
|
|
43
|
+
}
|
|
44
|
+
float3 desat(float3 c, float s) {
|
|
45
|
+
float l = dot(c, float3(0.299, 0.587, 0.114));
|
|
46
|
+
return mix(float3(l), c, s);
|
|
47
|
+
}
|
|
48
|
+
float3 saturate3(float3 c, float s) {
|
|
49
|
+
float l = dot(c, float3(0.299, 0.587, 0.114));
|
|
50
|
+
return clamp(mix(float3(l), c, s), 0.0, 1.0);
|
|
51
|
+
}
|
|
52
|
+
float2 rot2(float2 p, float a) {
|
|
53
|
+
float s = sin(a); float c = cos(a);
|
|
54
|
+
return float2(c * p.x + s * p.y, -s * p.x + c * p.y);
|
|
55
|
+
}
|
|
56
|
+
float3 vivid(float4 hsv) {
|
|
57
|
+
return hsv2rgb(float3(hsv.x, clamp(hsv.y + dark * 0.06, 0.0, 1.0), hsv.z));
|
|
58
|
+
}
|
|
59
|
+
// Waves spreading out from the last tap, fading within a second and a half.
|
|
60
|
+
float tapRipple(float2 p) {
|
|
61
|
+
float age = tapTime;
|
|
62
|
+
if (age > 1.5) return 0.0;
|
|
63
|
+
float d = distance(p, tap);
|
|
64
|
+
float front = age * 0.75;
|
|
65
|
+
float env = exp(-sq((d - front) / 0.14));
|
|
66
|
+
float decay = exp(-age * 2.6);
|
|
67
|
+
return sin(d * 34.0 - age * 24.0) * env * decay;
|
|
68
|
+
}
|
|
69
|
+
// A gentle ripple following a hovering pointer.
|
|
70
|
+
float hoverRipple(float2 p) {
|
|
71
|
+
if (hoverAmt <= 0.001) return 0.0;
|
|
72
|
+
float d = distance(p, hover);
|
|
73
|
+
float env = exp(-sq(d / 0.22));
|
|
74
|
+
return sin(d * 30.0 - time * 7.0) * env * hoverAmt * 0.6;
|
|
75
|
+
}
|
|
76
|
+
float4 permute(float4 x) { return mod(((x * 34.0) + 1.0) * x, 289.0); }
|
|
77
|
+
float4 taylorInvSqrt(float4 r) { return 1.79284291400159 - 0.85373472095314 * r; }
|
|
78
|
+
float snoise(float3 v) {
|
|
79
|
+
const float2 C = float2(1.0 / 6.0, 1.0 / 3.0);
|
|
80
|
+
const float4 D = float4(0.0, 0.5, 1.0, 2.0);
|
|
81
|
+
float3 i = floor(v + dot(v, C.yyy));
|
|
82
|
+
float3 x0 = v - i + dot(i, C.xxx);
|
|
83
|
+
float3 g = step(x0.yzx, x0.xyz);
|
|
84
|
+
float3 l = 1.0 - g;
|
|
85
|
+
float3 i1 = min(g.xyz, l.zxy);
|
|
86
|
+
float3 i2 = max(g.xyz, l.zxy);
|
|
87
|
+
float3 x1 = x0 - i1 + 1.0 * C.xxx;
|
|
88
|
+
float3 x2 = x0 - i2 + 2.0 * C.xxx;
|
|
89
|
+
float3 x3 = x0 - 1.0 + 3.0 * C.xxx;
|
|
90
|
+
i = mod(i, 289.0);
|
|
91
|
+
float4 p = permute(permute(permute(
|
|
92
|
+
i.z + float4(0.0, i1.z, i2.z, 1.0))
|
|
93
|
+
+ i.y + float4(0.0, i1.y, i2.y, 1.0))
|
|
94
|
+
+ i.x + float4(0.0, i1.x, i2.x, 1.0));
|
|
95
|
+
float n_ = 1.0 / 7.0;
|
|
96
|
+
float3 ns = n_ * D.wyz - D.xzx;
|
|
97
|
+
float4 j = p - 49.0 * floor(p * ns.z * ns.z);
|
|
98
|
+
float4 x_ = floor(j * ns.z);
|
|
99
|
+
float4 y_ = floor(j - 7.0 * x_);
|
|
100
|
+
float4 x = x_ * ns.x + ns.yyyy;
|
|
101
|
+
float4 y = y_ * ns.x + ns.yyyy;
|
|
102
|
+
float4 h = 1.0 - abs(x) - abs(y);
|
|
103
|
+
float4 b0 = float4(x.xy, y.xy);
|
|
104
|
+
float4 b1 = float4(x.zw, y.zw);
|
|
105
|
+
float4 s0 = floor(b0) * 2.0 + 1.0;
|
|
106
|
+
float4 s1 = floor(b1) * 2.0 + 1.0;
|
|
107
|
+
float4 sh = -step(h, float4(0.0));
|
|
108
|
+
float4 a0 = b0.xzyw + s0.xzyw * sh.xxyy;
|
|
109
|
+
float4 a1 = b1.xzyw + s1.xzyw * sh.zzww;
|
|
110
|
+
float3 p0 = float3(a0.xy, h.x);
|
|
111
|
+
float3 p1 = float3(a0.zw, h.y);
|
|
112
|
+
float3 p2 = float3(a1.xy, h.z);
|
|
113
|
+
float3 p3 = float3(a1.zw, h.w);
|
|
114
|
+
float4 norm = taylorInvSqrt(float4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3)));
|
|
115
|
+
p0 *= norm.x; p1 *= norm.y; p2 *= norm.z; p3 *= norm.w;
|
|
116
|
+
float4 m = max(0.6 - float4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0);
|
|
117
|
+
m = m * m;
|
|
118
|
+
return 42.0 * dot(m * m, float4(dot(p0, x0), dot(p1, x1), dot(p2, x2), dot(p3, x3)));
|
|
119
|
+
}
|
|
120
|
+
float speechEnv(float t) {
|
|
121
|
+
float gate = ss(-0.25, 0.35, snoise(float3(t * 0.6, 21.0, 0.0)));
|
|
122
|
+
float syl = 0.55 + 0.45 * snoise(float3(t * 3.0, 5.0, 0.0));
|
|
123
|
+
return clamp(gate * syl, 0.0, 1.0);
|
|
124
|
+
}
|
|
125
|
+
float2 coords(float2 pos) {
|
|
126
|
+
float m = min(resolution.x, resolution.y);
|
|
127
|
+
return float2(pos.x - 0.5 * resolution.x, 0.5 * resolution.y - pos.y) / m / scale;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
half4 main(float2 fragCoord) {
|
|
131
|
+
float2 q = coords(fragCoord);
|
|
132
|
+
float t = time;
|
|
133
|
+
float r = length(q);
|
|
134
|
+
|
|
135
|
+
float speak = ss(0.6, 1.0, react);
|
|
136
|
+
float speech = react * mix(0.55 + 0.25 * sin(t * 1.6), speechEnv(t), speak);
|
|
137
|
+
speech = mix(speech, mic, voice);
|
|
138
|
+
float speechSmooth = mix(speech, react * (0.55 + 0.25 * sin(t * 1.6)), 0.5 * speak);
|
|
139
|
+
float energy = 0.12 + 0.30 * react + 0.18 * flow + 0.28 * load + 0.06 * speechSmooth;
|
|
140
|
+
float R = 0.24 * (1.0 + speechSmooth * 0.20 + load * 0.05 * sin(t * 2.0) + react * 0.04 * sin(t * 1.6));
|
|
141
|
+
float p = r / R;
|
|
142
|
+
if (p > 2.2) return half4(0.0);
|
|
143
|
+
|
|
144
|
+
float core = exp(-2.0 * p * p);
|
|
145
|
+
float body = ss(1.06, 0.62, p);
|
|
146
|
+
|
|
147
|
+
float2 P = q / R;
|
|
148
|
+
float pl = length(P);
|
|
149
|
+
float nt = t * 0.15 + flowSpin * 0.05;
|
|
150
|
+
float nAmp = 0.20 + 0.28 * energy;
|
|
151
|
+
float2 sp = P + nAmp * float2(snoise(float3(P * 0.7, nt)), snoise(float3(P * 0.7 + 4.7, nt)));
|
|
152
|
+
float ang = atan(P.y, P.x);
|
|
153
|
+
float wave = (0.018 + 0.025 * energy) * sin(pl * 5.0 - t * 2.0) * ss(0.0, 0.4, pl);
|
|
154
|
+
sp += wave * float2(-sin(ang), cos(ang));
|
|
155
|
+
sp = rot2(sp, flowSpin * 0.30);
|
|
156
|
+
float2 gtoP = q - (hoverAmt > 0.001 ? hover : tap);
|
|
157
|
+
sp += (gtoP / (length(gtoP) + 1e-4)) * (tapRipple(q) + hoverRipple(q)) * 0.5;
|
|
158
|
+
float bt = t;
|
|
159
|
+
float a0 = orbSpin * 0.42 + 2.0 * snoise(float3(bt * 0.12, 0.0, 0.0));
|
|
160
|
+
float a1 = -orbSpin * 0.34 + 2.0 * snoise(float3(bt * 0.10, 5.0, 0.0)) + 2.0;
|
|
161
|
+
float a2 = orbSpin * 0.30 + 2.0 * snoise(float3(bt * 0.11, 9.0, 0.0)) + 1.0;
|
|
162
|
+
float a3 = -orbSpin * 0.26 + 2.0 * snoise(float3(bt * 0.09, 14.0, 0.0)) + 3.5;
|
|
163
|
+
float2 c0 = 0.40 * float2(cos(a0), sin(a0));
|
|
164
|
+
float2 c1 = 0.38 * float2(cos(a1), sin(a1));
|
|
165
|
+
float2 c2c = 0.36 * float2(cos(a2), sin(a2));
|
|
166
|
+
float2 c3c = 0.34 * float2(cos(a3), sin(a3));
|
|
167
|
+
float b0 = ss(1.15, 0.1, length(sp - c0));
|
|
168
|
+
float b1 = ss(1.30, 0.1, length(sp - c1));
|
|
169
|
+
float b2 = ss(1.20, 0.1, length(sp - c2c));
|
|
170
|
+
float b3 = ss(1.25, 0.1, length(sp - c3c));
|
|
171
|
+
|
|
172
|
+
float3 k0 = saturate3(vivid(col0), 1.7);
|
|
173
|
+
float3 kL = saturate3(clamp(vivid(col0) * 1.18, 0.0, 1.0), 1.3);
|
|
174
|
+
float3 kD = saturate3(vivid(col0) * 0.85, 1.3);
|
|
175
|
+
float3 colB = mix(kL, saturate3(vivid(col1), 1.7), clamp(count - 1.0, 0.0, 1.0));
|
|
176
|
+
float3 colC = mix(kD, saturate3(vivid(col2), 1.7), clamp(count - 2.0, 0.0, 1.0));
|
|
177
|
+
float3 colD = saturate3(vivid(col3), 1.7);
|
|
178
|
+
float3 colE = saturate3(vivid(col4), 1.7);
|
|
179
|
+
float3 base = saturate3(mix(vivid(col0), float3(1.0), 0.06), 1.3);
|
|
180
|
+
float3 col = base;
|
|
181
|
+
col = mix(col, k0, b0);
|
|
182
|
+
col = mix(col, colB, b1);
|
|
183
|
+
col = mix(col, colC, b0 * 0.5);
|
|
184
|
+
col = mix(col, colD, b2 * clamp(count - 3.0, 0.0, 1.0));
|
|
185
|
+
col = mix(col, colE, b3 * clamp(count - 4.0, 0.0, 1.0));
|
|
186
|
+
col = saturate3(col, 1.15);
|
|
187
|
+
col = clamp(col * 1.12, 0.0, 1.0);
|
|
188
|
+
col = desat(col, sat);
|
|
189
|
+
|
|
190
|
+
float z = sqrt(max(1.0 - p * p, 0.0));
|
|
191
|
+
float3 nrm = normalize(float3(q / R, z + 1e-4));
|
|
192
|
+
float3 Ld = normalize(float3(-0.35, 0.45, 0.85));
|
|
193
|
+
float diff = clamp(dot(nrm, Ld), 0.0, 1.0);
|
|
194
|
+
col *= 0.80 + 0.20 * diff;
|
|
195
|
+
float fres = pow(1.0 - clamp(nrm.z, 0.0, 1.0), 2.5);
|
|
196
|
+
col += fres * 0.10 * mix(float3(1.0), k0, 0.6);
|
|
197
|
+
|
|
198
|
+
float3 haloCol = mix(mix(vivid(col0), float3(1.0), 0.75), mix(vivid(col0), float3(1.0), 0.32), dark);
|
|
199
|
+
float halo = exp(-sq(max(r - R * 0.8, 0.0) / 0.09));
|
|
200
|
+
float haloA = halo * (mix(0.05, 0.10, dark) + 0.05 * react);
|
|
201
|
+
float waves = clamp(b0 + 0.8 * b1, 0.0, 1.0);
|
|
202
|
+
float bodyA = clamp(body * (0.15 + 0.60 * waves + 0.06 * core) * (0.92 + 0.08 * fres), 0.0, 1.0);
|
|
203
|
+
float fade = mix(1.0, 0.45 + 0.35 * sin(t * 2.0), load);
|
|
204
|
+
float3 pm = col * bodyA + haloCol * haloA * (1.0 - bodyA);
|
|
205
|
+
float al = bodyA + haloA * (1.0 - bodyA);
|
|
206
|
+
float gain = bright * fade;
|
|
207
|
+
return half4(half3(pm * gain), half(al * gain));
|
|
208
|
+
}
|
|
209
|
+
`;
|
|
210
|
+
|
|
211
|
+
let effect: SkRuntimeEffect | null | undefined;
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* The compiled shader, shared by every glow: it compiles once, the first time a glow draws. Null
|
|
215
|
+
* if Skia cannot compile it, in which case the glow draws nothing.
|
|
216
|
+
*/
|
|
217
|
+
export const getGlowEffect = () => {
|
|
218
|
+
if (effect === undefined) {
|
|
219
|
+
effect = Skia.RuntimeEffect.Make(SOURCE);
|
|
220
|
+
if (!effect && __DEV__) {
|
|
221
|
+
console.warn(
|
|
222
|
+
'[stream-chat-react-native/ai-components] VoiceGlow could not compile its shader, so it draws nothing. Please report this with your @shopify/react-native-skia version.',
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return effect;
|
|
227
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { snoise } from './simplex';
|
|
2
|
+
|
|
3
|
+
/** The shader math the wave runs on the CPU, once per frame, on the UI thread. */
|
|
4
|
+
|
|
5
|
+
export const mix = (a: number, b: number, t: number) => {
|
|
6
|
+
'worklet';
|
|
7
|
+
return a + (b - a) * t;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const fract = (x: number) => {
|
|
11
|
+
'worklet';
|
|
12
|
+
return x - Math.floor(x);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const clamp = (x: number, low: number, high: number) => {
|
|
16
|
+
'worklet';
|
|
17
|
+
return Math.min(Math.max(x, low), high);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/** GLSL's smoothstep, evaluated the same with its edges either way round. */
|
|
21
|
+
export const smoothstep = (edge0: number, edge1: number, x: number) => {
|
|
22
|
+
'worklet';
|
|
23
|
+
const t = clamp((x - edge0) / (edge1 - edge0), 0, 1);
|
|
24
|
+
return t * t * (3 - 2 * t);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/** HSV, hue a fraction of a turn, into `out` as RGB. */
|
|
28
|
+
export const hsvToRgb = (hue: number, saturation: number, value: number, out: number[]) => {
|
|
29
|
+
'worklet';
|
|
30
|
+
for (let i = 0; i < 3; i++) {
|
|
31
|
+
const k = i === 0 ? 1 : i === 1 ? 2 / 3 : 1 / 3;
|
|
32
|
+
const p = Math.abs(fract(hue + k) * 6 - 3);
|
|
33
|
+
out[i] = value * mix(1, clamp(p - 1, 0, 1), saturation);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** A palette color as is; dark mode lifts saturation a touch so it stays vivid on black. */
|
|
38
|
+
export const vivid = (
|
|
39
|
+
color: { hue: number; saturation: number; value: number },
|
|
40
|
+
dark: number,
|
|
41
|
+
out: number[],
|
|
42
|
+
) => {
|
|
43
|
+
'worklet';
|
|
44
|
+
hsvToRgb(color.hue, clamp(color.saturation + dark * 0.06, 0, 1), color.value, out);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/** Pushes `c` away from its luminance by `amount`; below 1 desaturates. */
|
|
48
|
+
export const saturate = (c: number[], amount: number) => {
|
|
49
|
+
'worklet';
|
|
50
|
+
const l = 0.299 * c[0] + 0.587 * c[1] + 0.114 * c[2];
|
|
51
|
+
for (let i = 0; i < 3; i++) c[i] = clamp(mix(l, c[i], amount), 0, 1);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* How much the agent is talking, 0 to 1: a slow phrase gate times a faster syllable wobble. The
|
|
56
|
+
* shader's own envelope, on the same simplex noise, so the wave and the glow speak alike.
|
|
57
|
+
*/
|
|
58
|
+
export const speechEnv = (t: number) => {
|
|
59
|
+
'worklet';
|
|
60
|
+
const gate = smoothstep(-0.25, 0.35, snoise(t * 0.6, 21, 0));
|
|
61
|
+
const syllable = 0.55 + 0.45 * snoise(t * 3, 5, 0);
|
|
62
|
+
return clamp(gate * syllable, 0, 1);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/** `expressivity` from 0 to 2; one that is not a number gets the default, 1. */
|
|
66
|
+
export const expressivityInput = (expressivity: number | undefined) =>
|
|
67
|
+
typeof expressivity === 'number' && Number.isFinite(expressivity) ? clamp(expressivity, 0, 2) : 1;
|
|
68
|
+
|
|
69
|
+
/** `level` from 0 to 1; one that is not a number counts as no live audio, so it can't stall the motion. */
|
|
70
|
+
export const audioLevelInput = (level: number | null | undefined) => {
|
|
71
|
+
'worklet';
|
|
72
|
+
return typeof level === 'number' && Number.isFinite(level) ? clamp(level, 0, 1) : null;
|
|
73
|
+
};
|