@fjandin/react-shader 0.0.14 → 0.0.16
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/dist/ReactShader.d.ts +1 -1
- package/dist/ReactShader.d.ts.map +1 -1
- package/dist/example/frontend.d.ts.map +1 -1
- package/dist/hooks/useAudio.d.ts +3 -0
- package/dist/hooks/useAudio.d.ts.map +1 -0
- package/dist/hooks/useWebGL.d.ts +1 -0
- package/dist/hooks/useWebGL.d.ts.map +1 -1
- package/dist/index.cjs +258 -32
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +254 -28
- package/dist/types.d.ts +30 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/ReactShader.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { ReactShaderProps } from "./types";
|
|
2
|
-
export declare function ReactShader({ className, fragment, vertex, uniforms, fullscreen, timeScale, onFrame, onClick, onMouseMove, onMouseDown, onMouseUp, }: ReactShaderProps): import("react/jsx-runtime").JSX.Element;
|
|
2
|
+
export declare function ReactShader({ className, fragment, vertex, uniforms, fullscreen, timeScale, onFrame, onClick, onMouseMove, onMouseDown, onMouseUp, onMouseWheel, }: ReactShaderProps): import("react/jsx-runtime").JSX.Element;
|
|
3
3
|
//# sourceMappingURL=ReactShader.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ReactShader.d.ts","sourceRoot":"","sources":["../src/ReactShader.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"ReactShader.d.ts","sourceRoot":"","sources":["../src/ReactShader.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAiC/C,wBAAgB,WAAW,CAAC,EAC1B,SAAS,EACT,QAAQ,EACR,MAAuB,EACvB,QAAQ,EACR,UAAkB,EAClB,SAAa,EACb,OAAO,EACP,OAAO,EACP,WAAW,EACX,WAAW,EACX,SAAS,EACT,YAAY,GACb,EAAE,gBAAgB,2CA0DlB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"frontend.d.ts","sourceRoot":"","sources":["../../src/example/frontend.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"frontend.d.ts","sourceRoot":"","sources":["../../src/example/frontend.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,aAAa,CAAA;AAkHpB,wBAAgB,GAAG,4CAiJlB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useAudio.d.ts","sourceRoot":"","sources":["../../src/hooks/useAudio.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAqC,eAAe,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AA6DlG,wBAAgB,QAAQ,CAAC,OAAO,GAAE,eAAoB,GAAG,cAAc,CAsMtE"}
|
package/dist/hooks/useWebGL.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ interface UseWebGLOptions {
|
|
|
9
9
|
onMouseDown?: (info: FrameInfo) => void;
|
|
10
10
|
onMouseUp?: (info: FrameInfo) => void;
|
|
11
11
|
onMouseMove?: (info: FrameInfo) => void;
|
|
12
|
+
onMouseWheel?: (info: FrameInfo, wheelDelta: number) => void;
|
|
12
13
|
timeScale?: number;
|
|
13
14
|
}
|
|
14
15
|
export declare function useWebGL(options: UseWebGLOptions): {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useWebGL.d.ts","sourceRoot":"","sources":["../../src/hooks/useWebGL.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAKvD,UAAU,eAAe;IACvB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;IACvC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;IAChC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAA;IACnC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAA;IACnC,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAA;IACvC,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAA;IACrC,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAA;IACvC,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAwED,wBAAgB,QAAQ,CAAC,OAAO,EAAE,eAAe;;;
|
|
1
|
+
{"version":3,"file":"useWebGL.d.ts","sourceRoot":"","sources":["../../src/hooks/useWebGL.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAKvD,UAAU,eAAe;IACvB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;IACvC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;IAChC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAA;IACnC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAA;IACnC,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAA;IACvC,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAA;IACrC,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAA;IACvC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,KAAK,IAAI,CAAA;IAC5D,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAwED,wBAAgB,QAAQ,CAAC,OAAO,EAAE,eAAe;;;EA0ShD"}
|
package/dist/index.cjs
CHANGED
|
@@ -29,6 +29,7 @@ var __export = (target, all) => {
|
|
|
29
29
|
// src/index.ts
|
|
30
30
|
var exports_src = {};
|
|
31
31
|
__export(exports_src, {
|
|
32
|
+
useAudio: () => useAudio,
|
|
32
33
|
generateUtilsFunction: () => generateUtilsFunction,
|
|
33
34
|
generateSimplexNoiseFunction: () => generateSimplexNoiseFunction,
|
|
34
35
|
generateSceneCirclesFunction: () => generateSceneCirclesFunction,
|
|
@@ -38,11 +39,219 @@ __export(exports_src, {
|
|
|
38
39
|
});
|
|
39
40
|
module.exports = __toCommonJS(exports_src);
|
|
40
41
|
|
|
42
|
+
// src/hooks/useAudio.ts
|
|
43
|
+
var import_react = require("react");
|
|
44
|
+
var DEFAULT_FFT_SIZE = 2048;
|
|
45
|
+
var DEFAULT_SMOOTHING_TIME_CONSTANT = 0.8;
|
|
46
|
+
var DEFAULT_SMOOTHING = 0.9;
|
|
47
|
+
var NUM_BANDS = 16;
|
|
48
|
+
var MIN_FREQ = 20;
|
|
49
|
+
var MAX_FREQ = 20000;
|
|
50
|
+
var DEFAULT_BANDS = {
|
|
51
|
+
low: [20, 250],
|
|
52
|
+
mid: [250, 4000],
|
|
53
|
+
high: [4000, 20000]
|
|
54
|
+
};
|
|
55
|
+
var EMPTY_BANDS = Array(NUM_BANDS).fill(0);
|
|
56
|
+
function hzToBin(hz, sampleRate, fftSize) {
|
|
57
|
+
const binFrequency = sampleRate / fftSize;
|
|
58
|
+
return Math.round(hz / binFrequency);
|
|
59
|
+
}
|
|
60
|
+
function calculateBandLevel(data, startBin, endBin) {
|
|
61
|
+
if (startBin >= endBin || startBin >= data.length)
|
|
62
|
+
return 0;
|
|
63
|
+
const clampedStart = Math.max(0, startBin);
|
|
64
|
+
const clampedEnd = Math.min(data.length, endBin);
|
|
65
|
+
let sum = 0;
|
|
66
|
+
for (let i = clampedStart;i < clampedEnd; i++) {
|
|
67
|
+
sum += data[i];
|
|
68
|
+
}
|
|
69
|
+
const count = clampedEnd - clampedStart;
|
|
70
|
+
return count > 0 ? sum / count / 255 : 0;
|
|
71
|
+
}
|
|
72
|
+
function lerp(a, b, t) {
|
|
73
|
+
return a + (b - a) * t;
|
|
74
|
+
}
|
|
75
|
+
function getLogFrequencyBands() {
|
|
76
|
+
const logMin = Math.log10(MIN_FREQ);
|
|
77
|
+
const logMax = Math.log10(MAX_FREQ);
|
|
78
|
+
const step = (logMax - logMin) / NUM_BANDS;
|
|
79
|
+
const frequencies = [];
|
|
80
|
+
for (let i = 0;i <= NUM_BANDS; i++) {
|
|
81
|
+
frequencies.push(10 ** (logMin + step * i));
|
|
82
|
+
}
|
|
83
|
+
return frequencies;
|
|
84
|
+
}
|
|
85
|
+
var LOG_FREQ_BANDS = getLogFrequencyBands();
|
|
86
|
+
function useAudio(options = {}) {
|
|
87
|
+
const {
|
|
88
|
+
source = "microphone",
|
|
89
|
+
mediaElement = null,
|
|
90
|
+
fftSize = DEFAULT_FFT_SIZE,
|
|
91
|
+
smoothingTimeConstant = DEFAULT_SMOOTHING_TIME_CONSTANT,
|
|
92
|
+
smoothing = DEFAULT_SMOOTHING,
|
|
93
|
+
frequencyBands = {}
|
|
94
|
+
} = options;
|
|
95
|
+
const bands = {
|
|
96
|
+
low: frequencyBands.low ?? DEFAULT_BANDS.low,
|
|
97
|
+
mid: frequencyBands.mid ?? DEFAULT_BANDS.mid,
|
|
98
|
+
high: frequencyBands.high ?? DEFAULT_BANDS.high
|
|
99
|
+
};
|
|
100
|
+
const [state, setState] = import_react.useState("disconnected");
|
|
101
|
+
const [error, setError] = import_react.useState(null);
|
|
102
|
+
const [isRunning, setIsRunning] = import_react.useState(false);
|
|
103
|
+
const [levels, setLevels] = import_react.useState({ low: 0, mid: 0, high: 0, bands: [...EMPTY_BANDS] });
|
|
104
|
+
const [frequencyData, setFrequencyData] = import_react.useState(null);
|
|
105
|
+
const audioStateRef = import_react.useRef(null);
|
|
106
|
+
const animationFrameRef = import_react.useRef(0);
|
|
107
|
+
const dataArrayRef = import_react.useRef(null);
|
|
108
|
+
const smoothedLevelsRef = import_react.useRef({ low: 0, mid: 0, high: 0, bands: [...EMPTY_BANDS] });
|
|
109
|
+
const analyze = import_react.useCallback(() => {
|
|
110
|
+
const audioState = audioStateRef.current;
|
|
111
|
+
if (!audioState)
|
|
112
|
+
return;
|
|
113
|
+
const { analyser } = audioState;
|
|
114
|
+
const dataArray = dataArrayRef.current;
|
|
115
|
+
if (!dataArray)
|
|
116
|
+
return;
|
|
117
|
+
analyser.getByteFrequencyData(dataArray);
|
|
118
|
+
const sampleRate = audioState.context.sampleRate;
|
|
119
|
+
const fftBins = analyser.fftSize;
|
|
120
|
+
const lowBins = [
|
|
121
|
+
hzToBin(bands.low[0], sampleRate, fftBins),
|
|
122
|
+
hzToBin(bands.low[1], sampleRate, fftBins)
|
|
123
|
+
];
|
|
124
|
+
const midBins = [
|
|
125
|
+
hzToBin(bands.mid[0], sampleRate, fftBins),
|
|
126
|
+
hzToBin(bands.mid[1], sampleRate, fftBins)
|
|
127
|
+
];
|
|
128
|
+
const highBins = [
|
|
129
|
+
hzToBin(bands.high[0], sampleRate, fftBins),
|
|
130
|
+
hzToBin(bands.high[1], sampleRate, fftBins)
|
|
131
|
+
];
|
|
132
|
+
const rawBands = [];
|
|
133
|
+
for (let i = 0;i < NUM_BANDS; i++) {
|
|
134
|
+
const startBin = hzToBin(LOG_FREQ_BANDS[i], sampleRate, fftBins);
|
|
135
|
+
const endBin = hzToBin(LOG_FREQ_BANDS[i + 1], sampleRate, fftBins);
|
|
136
|
+
rawBands.push(calculateBandLevel(dataArray, startBin, endBin));
|
|
137
|
+
}
|
|
138
|
+
const rawLevels = {
|
|
139
|
+
low: calculateBandLevel(dataArray, lowBins[0], lowBins[1]),
|
|
140
|
+
mid: calculateBandLevel(dataArray, midBins[0], midBins[1]),
|
|
141
|
+
high: calculateBandLevel(dataArray, highBins[0], highBins[1]),
|
|
142
|
+
bands: rawBands
|
|
143
|
+
};
|
|
144
|
+
const t = 1 - smoothing;
|
|
145
|
+
const prev = smoothedLevelsRef.current;
|
|
146
|
+
const smoothedBands = rawBands.map((raw, i) => lerp(prev.bands[i] ?? 0, raw, t));
|
|
147
|
+
const smoothedLevels = {
|
|
148
|
+
low: lerp(prev.low, rawLevels.low, t),
|
|
149
|
+
mid: lerp(prev.mid, rawLevels.mid, t),
|
|
150
|
+
high: lerp(prev.high, rawLevels.high, t),
|
|
151
|
+
bands: smoothedBands
|
|
152
|
+
};
|
|
153
|
+
smoothedLevelsRef.current = smoothedLevels;
|
|
154
|
+
setLevels(smoothedLevels);
|
|
155
|
+
const dataCopy = new Uint8Array(dataArray.length);
|
|
156
|
+
dataCopy.set(dataArray);
|
|
157
|
+
setFrequencyData(dataCopy);
|
|
158
|
+
animationFrameRef.current = requestAnimationFrame(analyze);
|
|
159
|
+
}, [bands.low, bands.mid, bands.high, smoothing]);
|
|
160
|
+
const start = import_react.useCallback(async () => {
|
|
161
|
+
if (audioStateRef.current)
|
|
162
|
+
return;
|
|
163
|
+
setState("connecting");
|
|
164
|
+
setError(null);
|
|
165
|
+
try {
|
|
166
|
+
const AudioContextClass = window.AudioContext || window.webkitAudioContext;
|
|
167
|
+
if (!AudioContextClass) {
|
|
168
|
+
throw new Error("AudioContext not supported");
|
|
169
|
+
}
|
|
170
|
+
const context = new AudioContextClass;
|
|
171
|
+
if (context.state === "suspended") {
|
|
172
|
+
await context.resume();
|
|
173
|
+
}
|
|
174
|
+
const analyser = context.createAnalyser();
|
|
175
|
+
analyser.fftSize = fftSize;
|
|
176
|
+
analyser.smoothingTimeConstant = smoothingTimeConstant;
|
|
177
|
+
let audioSource;
|
|
178
|
+
let stream;
|
|
179
|
+
if (source === "microphone") {
|
|
180
|
+
stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
181
|
+
audioSource = context.createMediaStreamSource(stream);
|
|
182
|
+
} else if (source === "display") {
|
|
183
|
+
stream = await navigator.mediaDevices.getDisplayMedia({
|
|
184
|
+
video: true,
|
|
185
|
+
audio: true
|
|
186
|
+
});
|
|
187
|
+
const audioTracks = stream.getAudioTracks();
|
|
188
|
+
if (audioTracks.length === 0) {
|
|
189
|
+
for (const track of stream.getTracks()) {
|
|
190
|
+
track.stop();
|
|
191
|
+
}
|
|
192
|
+
throw new Error("No audio track available. Make sure to share a tab with audio enabled.");
|
|
193
|
+
}
|
|
194
|
+
audioSource = context.createMediaStreamSource(stream);
|
|
195
|
+
} else if (source === "element" && mediaElement) {
|
|
196
|
+
audioSource = context.createMediaElementSource(mediaElement);
|
|
197
|
+
audioSource.connect(context.destination);
|
|
198
|
+
} else {
|
|
199
|
+
throw new Error("Invalid audio source configuration");
|
|
200
|
+
}
|
|
201
|
+
audioSource.connect(analyser);
|
|
202
|
+
audioStateRef.current = {
|
|
203
|
+
context,
|
|
204
|
+
analyser,
|
|
205
|
+
source: audioSource,
|
|
206
|
+
stream
|
|
207
|
+
};
|
|
208
|
+
dataArrayRef.current = new Uint8Array(analyser.frequencyBinCount);
|
|
209
|
+
setState("connected");
|
|
210
|
+
setIsRunning(true);
|
|
211
|
+
animationFrameRef.current = requestAnimationFrame(analyze);
|
|
212
|
+
} catch (err) {
|
|
213
|
+
const audioError = err instanceof Error ? err : new Error(String(err));
|
|
214
|
+
setError(audioError);
|
|
215
|
+
setState("error");
|
|
216
|
+
setIsRunning(false);
|
|
217
|
+
}
|
|
218
|
+
}, [source, mediaElement, fftSize, smoothingTimeConstant, analyze]);
|
|
219
|
+
const stop = import_react.useCallback(() => {
|
|
220
|
+
cancelAnimationFrame(animationFrameRef.current);
|
|
221
|
+
const audioState = audioStateRef.current;
|
|
222
|
+
if (audioState) {
|
|
223
|
+
audioState.source.disconnect();
|
|
224
|
+
if (audioState.stream) {
|
|
225
|
+
for (const track of audioState.stream.getTracks()) {
|
|
226
|
+
track.stop();
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
audioState.analyser.disconnect();
|
|
230
|
+
audioState.context.close();
|
|
231
|
+
audioStateRef.current = null;
|
|
232
|
+
}
|
|
233
|
+
dataArrayRef.current = null;
|
|
234
|
+
smoothedLevelsRef.current = { low: 0, mid: 0, high: 0, bands: [...EMPTY_BANDS] };
|
|
235
|
+
setState("disconnected");
|
|
236
|
+
setIsRunning(false);
|
|
237
|
+
setLevels({ low: 0, mid: 0, high: 0, bands: [...EMPTY_BANDS] });
|
|
238
|
+
setFrequencyData(null);
|
|
239
|
+
}, []);
|
|
240
|
+
return {
|
|
241
|
+
levels,
|
|
242
|
+
frequencyData,
|
|
243
|
+
state,
|
|
244
|
+
error,
|
|
245
|
+
start,
|
|
246
|
+
stop,
|
|
247
|
+
isRunning
|
|
248
|
+
};
|
|
249
|
+
}
|
|
41
250
|
// src/ReactShader.tsx
|
|
42
|
-
var
|
|
251
|
+
var import_react3 = require("react");
|
|
43
252
|
|
|
44
253
|
// src/hooks/useWebGL.ts
|
|
45
|
-
var
|
|
254
|
+
var import_react2 = require("react");
|
|
46
255
|
|
|
47
256
|
// src/utils/shader.ts
|
|
48
257
|
function compileShader(gl, type, source) {
|
|
@@ -407,28 +616,29 @@ function cleanupWebGL(gl, state) {
|
|
|
407
616
|
gl.deleteProgram(state.program);
|
|
408
617
|
}
|
|
409
618
|
function useWebGL(options) {
|
|
410
|
-
const canvasRef =
|
|
411
|
-
const stateRef =
|
|
412
|
-
const animationFrameRef =
|
|
413
|
-
const elapsedTimeRef =
|
|
414
|
-
const lastFrameTimeRef =
|
|
415
|
-
const mouseRef =
|
|
416
|
-
const mouseNormalizedRef =
|
|
417
|
-
const mouseLeftDownRef =
|
|
418
|
-
const canvasRectRef =
|
|
419
|
-
const contextLostRef =
|
|
420
|
-
const uniformsRef =
|
|
421
|
-
const onErrorRef =
|
|
422
|
-
const onFrameRef =
|
|
423
|
-
const onClickRef =
|
|
424
|
-
const onMouseDownRef =
|
|
425
|
-
const onMouseUpRef =
|
|
426
|
-
const onMouseMoveRef =
|
|
427
|
-
const
|
|
428
|
-
const
|
|
429
|
-
const
|
|
430
|
-
const
|
|
431
|
-
const
|
|
619
|
+
const canvasRef = import_react2.useRef(null);
|
|
620
|
+
const stateRef = import_react2.useRef(null);
|
|
621
|
+
const animationFrameRef = import_react2.useRef(0);
|
|
622
|
+
const elapsedTimeRef = import_react2.useRef(0);
|
|
623
|
+
const lastFrameTimeRef = import_react2.useRef(0);
|
|
624
|
+
const mouseRef = import_react2.useRef([0, 0]);
|
|
625
|
+
const mouseNormalizedRef = import_react2.useRef([0, 0]);
|
|
626
|
+
const mouseLeftDownRef = import_react2.useRef(false);
|
|
627
|
+
const canvasRectRef = import_react2.useRef(null);
|
|
628
|
+
const contextLostRef = import_react2.useRef(false);
|
|
629
|
+
const uniformsRef = import_react2.useRef(options.uniforms);
|
|
630
|
+
const onErrorRef = import_react2.useRef(options.onError);
|
|
631
|
+
const onFrameRef = import_react2.useRef(options.onFrame);
|
|
632
|
+
const onClickRef = import_react2.useRef(options.onClick);
|
|
633
|
+
const onMouseDownRef = import_react2.useRef(options.onMouseDown);
|
|
634
|
+
const onMouseUpRef = import_react2.useRef(options.onMouseUp);
|
|
635
|
+
const onMouseMoveRef = import_react2.useRef(options.onMouseMove);
|
|
636
|
+
const onMouseWheelRef = import_react2.useRef(options.onMouseWheel);
|
|
637
|
+
const timeScaleRef = import_react2.useRef(options.timeScale ?? 1);
|
|
638
|
+
const vertexRef = import_react2.useRef(options.vertex);
|
|
639
|
+
const fragmentRef = import_react2.useRef(options.fragment);
|
|
640
|
+
const dprRef = import_react2.useRef(window.devicePixelRatio || 1);
|
|
641
|
+
const defaultUniformsRef = import_react2.useRef({
|
|
432
642
|
iTime: 0,
|
|
433
643
|
iMouse: [0, 0],
|
|
434
644
|
iMouseNormalized: [0, 0],
|
|
@@ -442,10 +652,11 @@ function useWebGL(options) {
|
|
|
442
652
|
onMouseDownRef.current = options.onMouseDown;
|
|
443
653
|
onMouseUpRef.current = options.onMouseUp;
|
|
444
654
|
onMouseMoveRef.current = options.onMouseMove;
|
|
655
|
+
onMouseWheelRef.current = options.onMouseWheel;
|
|
445
656
|
timeScaleRef.current = options.timeScale ?? 1;
|
|
446
657
|
vertexRef.current = options.vertex;
|
|
447
658
|
fragmentRef.current = options.fragment;
|
|
448
|
-
const render =
|
|
659
|
+
const render = import_react2.useCallback((time) => {
|
|
449
660
|
if (contextLostRef.current)
|
|
450
661
|
return;
|
|
451
662
|
const state = stateRef.current;
|
|
@@ -497,7 +708,7 @@ function useWebGL(options) {
|
|
|
497
708
|
}
|
|
498
709
|
animationFrameRef.current = requestAnimationFrame(render);
|
|
499
710
|
}, []);
|
|
500
|
-
|
|
711
|
+
import_react2.useEffect(() => {
|
|
501
712
|
const canvas = canvasRef.current;
|
|
502
713
|
if (!canvas)
|
|
503
714
|
return;
|
|
@@ -545,7 +756,7 @@ function useWebGL(options) {
|
|
|
545
756
|
}
|
|
546
757
|
};
|
|
547
758
|
}, [render]);
|
|
548
|
-
|
|
759
|
+
import_react2.useEffect(() => {
|
|
549
760
|
const canvas = canvasRef.current;
|
|
550
761
|
if (!canvas)
|
|
551
762
|
return;
|
|
@@ -616,10 +827,21 @@ function useWebGL(options) {
|
|
|
616
827
|
mouseLeftDown: mouseLeftDownRef.current
|
|
617
828
|
});
|
|
618
829
|
};
|
|
830
|
+
const handleMouseWheel = (event) => {
|
|
831
|
+
onMouseWheelRef.current?.({
|
|
832
|
+
deltaTime: 0,
|
|
833
|
+
time: elapsedTimeRef.current,
|
|
834
|
+
resolution: [canvas.width, canvas.height],
|
|
835
|
+
mouse: mouseRef.current,
|
|
836
|
+
mouseNormalized: mouseNormalizedRef.current,
|
|
837
|
+
mouseLeftDown: mouseLeftDownRef.current
|
|
838
|
+
}, event.deltaY);
|
|
839
|
+
};
|
|
619
840
|
window.addEventListener("mousemove", handleMouseMove);
|
|
620
841
|
window.addEventListener("mousedown", handleMouseDown);
|
|
621
842
|
window.addEventListener("mouseup", handleMouseUp);
|
|
622
843
|
canvas.addEventListener("click", handleClick);
|
|
844
|
+
window.addEventListener("wheel", handleMouseWheel);
|
|
623
845
|
return () => {
|
|
624
846
|
resizeObserver.disconnect();
|
|
625
847
|
window.removeEventListener("scroll", updateRect);
|
|
@@ -627,6 +849,7 @@ function useWebGL(options) {
|
|
|
627
849
|
window.removeEventListener("mousedown", handleMouseDown);
|
|
628
850
|
window.removeEventListener("mouseup", handleMouseUp);
|
|
629
851
|
canvas.removeEventListener("click", handleClick);
|
|
852
|
+
window.removeEventListener("wheel", handleMouseWheel);
|
|
630
853
|
};
|
|
631
854
|
}, []);
|
|
632
855
|
return { canvasRef, mouseRef };
|
|
@@ -635,6 +858,7 @@ function useWebGL(options) {
|
|
|
635
858
|
// src/ReactShader.tsx
|
|
636
859
|
var jsx_dev_runtime = require("react/jsx-dev-runtime");
|
|
637
860
|
var DEFAULT_VERTEX = `#version 300 es
|
|
861
|
+
precision highp float;
|
|
638
862
|
in vec2 a_position;
|
|
639
863
|
|
|
640
864
|
void main() {
|
|
@@ -670,14 +894,15 @@ function ReactShader({
|
|
|
670
894
|
onClick,
|
|
671
895
|
onMouseMove,
|
|
672
896
|
onMouseDown,
|
|
673
|
-
onMouseUp
|
|
897
|
+
onMouseUp,
|
|
898
|
+
onMouseWheel
|
|
674
899
|
}) {
|
|
675
|
-
const [error, setError] =
|
|
676
|
-
const handleError =
|
|
900
|
+
const [error, setError] = import_react3.useState(null);
|
|
901
|
+
const handleError = import_react3.useCallback((err) => {
|
|
677
902
|
setError(err.message);
|
|
678
903
|
console.error("ReactShader error:", err);
|
|
679
904
|
}, []);
|
|
680
|
-
|
|
905
|
+
import_react3.useEffect(() => {
|
|
681
906
|
setError(null);
|
|
682
907
|
}, [fragment, vertex]);
|
|
683
908
|
const { canvasRef } = useWebGL({
|
|
@@ -690,9 +915,10 @@ function ReactShader({
|
|
|
690
915
|
onMouseMove,
|
|
691
916
|
onMouseDown,
|
|
692
917
|
onMouseUp,
|
|
918
|
+
onMouseWheel,
|
|
693
919
|
timeScale
|
|
694
920
|
});
|
|
695
|
-
const containerStyle =
|
|
921
|
+
const containerStyle = import_react3.useMemo(() => fullscreen ? FULLSCREEN_CONTAINER_STYLE : DEFAULT_CONTAINER_STYLE, [fullscreen]);
|
|
696
922
|
if (error) {
|
|
697
923
|
return /* @__PURE__ */ jsx_dev_runtime.jsxDEV("div", {
|
|
698
924
|
className,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
export { useAudio } from "./hooks/useAudio";
|
|
1
2
|
export { ReactShader } from "./ReactShader";
|
|
2
3
|
export { generateColorPaletteFunction } from "./shaders/color-palette";
|
|
3
4
|
export { generateDistortionRippleFunction } from "./shaders/distortion-ripple";
|
|
4
5
|
export { generateSceneCirclesFunction } from "./shaders/scene-circles";
|
|
5
6
|
export { generateSimplexNoiseFunction } from "./shaders/simplex-noise";
|
|
6
7
|
export { generateUtilsFunction } from "./shaders/utils";
|
|
7
|
-
export type { DefaultUniforms, FloatArray, FrameInfo, ReactShaderProps, TextureMagFilter, TextureMinFilter, TextureOptions, TextureSource, TextureWrap, UniformValue, Vec2, Vec2Array, Vec3, Vec3Array, Vec4, Vec4Array, } from "./types";
|
|
8
|
+
export type { AudioConnectionState, AudioLevels, AudioSourceType, DefaultUniforms, FloatArray, FrameInfo, ReactShaderProps, TextureMagFilter, TextureMinFilter, TextureOptions, TextureSource, TextureWrap, UniformValue, UseAudioOptions, UseAudioReturn, Vec2, Vec2Array, Vec3, Vec3Array, Vec4, Vec4Array, } from "./types";
|
|
8
9
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC3C,OAAO,EAAE,4BAA4B,EAAE,MAAM,yBAAyB,CAAA;AACtE,OAAO,EAAE,gCAAgC,EAAE,MAAM,6BAA6B,CAAA;AAC9E,OAAO,EAAE,4BAA4B,EAAE,MAAM,yBAAyB,CAAA;AACtE,OAAO,EAAE,4BAA4B,EAAE,MAAM,yBAAyB,CAAA;AACtE,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAA;AACvD,YAAY,EACV,eAAe,EACf,UAAU,EACV,SAAS,EACT,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,cAAc,EACd,aAAa,EACb,WAAW,EACX,YAAY,EACZ,IAAI,EACJ,SAAS,EACT,IAAI,EACJ,SAAS,EACT,IAAI,EACJ,SAAS,GACV,MAAM,SAAS,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC3C,OAAO,EAAE,4BAA4B,EAAE,MAAM,yBAAyB,CAAA;AACtE,OAAO,EAAE,gCAAgC,EAAE,MAAM,6BAA6B,CAAA;AAC9E,OAAO,EAAE,4BAA4B,EAAE,MAAM,yBAAyB,CAAA;AACtE,OAAO,EAAE,4BAA4B,EAAE,MAAM,yBAAyB,CAAA;AACtE,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAA;AACvD,YAAY,EACV,oBAAoB,EACpB,WAAW,EACX,eAAe,EACf,eAAe,EACf,UAAU,EACV,SAAS,EACT,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,cAAc,EACd,aAAa,EACb,WAAW,EACX,YAAY,EACZ,eAAe,EACf,cAAc,EACd,IAAI,EACJ,SAAS,EACT,IAAI,EACJ,SAAS,EACT,IAAI,EACJ,SAAS,GACV,MAAM,SAAS,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,216 @@
|
|
|
1
|
+
// src/hooks/useAudio.ts
|
|
2
|
+
import { useCallback, useRef, useState } from "react";
|
|
3
|
+
var DEFAULT_FFT_SIZE = 2048;
|
|
4
|
+
var DEFAULT_SMOOTHING_TIME_CONSTANT = 0.8;
|
|
5
|
+
var DEFAULT_SMOOTHING = 0.9;
|
|
6
|
+
var NUM_BANDS = 16;
|
|
7
|
+
var MIN_FREQ = 20;
|
|
8
|
+
var MAX_FREQ = 20000;
|
|
9
|
+
var DEFAULT_BANDS = {
|
|
10
|
+
low: [20, 250],
|
|
11
|
+
mid: [250, 4000],
|
|
12
|
+
high: [4000, 20000]
|
|
13
|
+
};
|
|
14
|
+
var EMPTY_BANDS = Array(NUM_BANDS).fill(0);
|
|
15
|
+
function hzToBin(hz, sampleRate, fftSize) {
|
|
16
|
+
const binFrequency = sampleRate / fftSize;
|
|
17
|
+
return Math.round(hz / binFrequency);
|
|
18
|
+
}
|
|
19
|
+
function calculateBandLevel(data, startBin, endBin) {
|
|
20
|
+
if (startBin >= endBin || startBin >= data.length)
|
|
21
|
+
return 0;
|
|
22
|
+
const clampedStart = Math.max(0, startBin);
|
|
23
|
+
const clampedEnd = Math.min(data.length, endBin);
|
|
24
|
+
let sum = 0;
|
|
25
|
+
for (let i = clampedStart;i < clampedEnd; i++) {
|
|
26
|
+
sum += data[i];
|
|
27
|
+
}
|
|
28
|
+
const count = clampedEnd - clampedStart;
|
|
29
|
+
return count > 0 ? sum / count / 255 : 0;
|
|
30
|
+
}
|
|
31
|
+
function lerp(a, b, t) {
|
|
32
|
+
return a + (b - a) * t;
|
|
33
|
+
}
|
|
34
|
+
function getLogFrequencyBands() {
|
|
35
|
+
const logMin = Math.log10(MIN_FREQ);
|
|
36
|
+
const logMax = Math.log10(MAX_FREQ);
|
|
37
|
+
const step = (logMax - logMin) / NUM_BANDS;
|
|
38
|
+
const frequencies = [];
|
|
39
|
+
for (let i = 0;i <= NUM_BANDS; i++) {
|
|
40
|
+
frequencies.push(10 ** (logMin + step * i));
|
|
41
|
+
}
|
|
42
|
+
return frequencies;
|
|
43
|
+
}
|
|
44
|
+
var LOG_FREQ_BANDS = getLogFrequencyBands();
|
|
45
|
+
function useAudio(options = {}) {
|
|
46
|
+
const {
|
|
47
|
+
source = "microphone",
|
|
48
|
+
mediaElement = null,
|
|
49
|
+
fftSize = DEFAULT_FFT_SIZE,
|
|
50
|
+
smoothingTimeConstant = DEFAULT_SMOOTHING_TIME_CONSTANT,
|
|
51
|
+
smoothing = DEFAULT_SMOOTHING,
|
|
52
|
+
frequencyBands = {}
|
|
53
|
+
} = options;
|
|
54
|
+
const bands = {
|
|
55
|
+
low: frequencyBands.low ?? DEFAULT_BANDS.low,
|
|
56
|
+
mid: frequencyBands.mid ?? DEFAULT_BANDS.mid,
|
|
57
|
+
high: frequencyBands.high ?? DEFAULT_BANDS.high
|
|
58
|
+
};
|
|
59
|
+
const [state, setState] = useState("disconnected");
|
|
60
|
+
const [error, setError] = useState(null);
|
|
61
|
+
const [isRunning, setIsRunning] = useState(false);
|
|
62
|
+
const [levels, setLevels] = useState({ low: 0, mid: 0, high: 0, bands: [...EMPTY_BANDS] });
|
|
63
|
+
const [frequencyData, setFrequencyData] = useState(null);
|
|
64
|
+
const audioStateRef = useRef(null);
|
|
65
|
+
const animationFrameRef = useRef(0);
|
|
66
|
+
const dataArrayRef = useRef(null);
|
|
67
|
+
const smoothedLevelsRef = useRef({ low: 0, mid: 0, high: 0, bands: [...EMPTY_BANDS] });
|
|
68
|
+
const analyze = useCallback(() => {
|
|
69
|
+
const audioState = audioStateRef.current;
|
|
70
|
+
if (!audioState)
|
|
71
|
+
return;
|
|
72
|
+
const { analyser } = audioState;
|
|
73
|
+
const dataArray = dataArrayRef.current;
|
|
74
|
+
if (!dataArray)
|
|
75
|
+
return;
|
|
76
|
+
analyser.getByteFrequencyData(dataArray);
|
|
77
|
+
const sampleRate = audioState.context.sampleRate;
|
|
78
|
+
const fftBins = analyser.fftSize;
|
|
79
|
+
const lowBins = [
|
|
80
|
+
hzToBin(bands.low[0], sampleRate, fftBins),
|
|
81
|
+
hzToBin(bands.low[1], sampleRate, fftBins)
|
|
82
|
+
];
|
|
83
|
+
const midBins = [
|
|
84
|
+
hzToBin(bands.mid[0], sampleRate, fftBins),
|
|
85
|
+
hzToBin(bands.mid[1], sampleRate, fftBins)
|
|
86
|
+
];
|
|
87
|
+
const highBins = [
|
|
88
|
+
hzToBin(bands.high[0], sampleRate, fftBins),
|
|
89
|
+
hzToBin(bands.high[1], sampleRate, fftBins)
|
|
90
|
+
];
|
|
91
|
+
const rawBands = [];
|
|
92
|
+
for (let i = 0;i < NUM_BANDS; i++) {
|
|
93
|
+
const startBin = hzToBin(LOG_FREQ_BANDS[i], sampleRate, fftBins);
|
|
94
|
+
const endBin = hzToBin(LOG_FREQ_BANDS[i + 1], sampleRate, fftBins);
|
|
95
|
+
rawBands.push(calculateBandLevel(dataArray, startBin, endBin));
|
|
96
|
+
}
|
|
97
|
+
const rawLevels = {
|
|
98
|
+
low: calculateBandLevel(dataArray, lowBins[0], lowBins[1]),
|
|
99
|
+
mid: calculateBandLevel(dataArray, midBins[0], midBins[1]),
|
|
100
|
+
high: calculateBandLevel(dataArray, highBins[0], highBins[1]),
|
|
101
|
+
bands: rawBands
|
|
102
|
+
};
|
|
103
|
+
const t = 1 - smoothing;
|
|
104
|
+
const prev = smoothedLevelsRef.current;
|
|
105
|
+
const smoothedBands = rawBands.map((raw, i) => lerp(prev.bands[i] ?? 0, raw, t));
|
|
106
|
+
const smoothedLevels = {
|
|
107
|
+
low: lerp(prev.low, rawLevels.low, t),
|
|
108
|
+
mid: lerp(prev.mid, rawLevels.mid, t),
|
|
109
|
+
high: lerp(prev.high, rawLevels.high, t),
|
|
110
|
+
bands: smoothedBands
|
|
111
|
+
};
|
|
112
|
+
smoothedLevelsRef.current = smoothedLevels;
|
|
113
|
+
setLevels(smoothedLevels);
|
|
114
|
+
const dataCopy = new Uint8Array(dataArray.length);
|
|
115
|
+
dataCopy.set(dataArray);
|
|
116
|
+
setFrequencyData(dataCopy);
|
|
117
|
+
animationFrameRef.current = requestAnimationFrame(analyze);
|
|
118
|
+
}, [bands.low, bands.mid, bands.high, smoothing]);
|
|
119
|
+
const start = useCallback(async () => {
|
|
120
|
+
if (audioStateRef.current)
|
|
121
|
+
return;
|
|
122
|
+
setState("connecting");
|
|
123
|
+
setError(null);
|
|
124
|
+
try {
|
|
125
|
+
const AudioContextClass = window.AudioContext || window.webkitAudioContext;
|
|
126
|
+
if (!AudioContextClass) {
|
|
127
|
+
throw new Error("AudioContext not supported");
|
|
128
|
+
}
|
|
129
|
+
const context = new AudioContextClass;
|
|
130
|
+
if (context.state === "suspended") {
|
|
131
|
+
await context.resume();
|
|
132
|
+
}
|
|
133
|
+
const analyser = context.createAnalyser();
|
|
134
|
+
analyser.fftSize = fftSize;
|
|
135
|
+
analyser.smoothingTimeConstant = smoothingTimeConstant;
|
|
136
|
+
let audioSource;
|
|
137
|
+
let stream;
|
|
138
|
+
if (source === "microphone") {
|
|
139
|
+
stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
140
|
+
audioSource = context.createMediaStreamSource(stream);
|
|
141
|
+
} else if (source === "display") {
|
|
142
|
+
stream = await navigator.mediaDevices.getDisplayMedia({
|
|
143
|
+
video: true,
|
|
144
|
+
audio: true
|
|
145
|
+
});
|
|
146
|
+
const audioTracks = stream.getAudioTracks();
|
|
147
|
+
if (audioTracks.length === 0) {
|
|
148
|
+
for (const track of stream.getTracks()) {
|
|
149
|
+
track.stop();
|
|
150
|
+
}
|
|
151
|
+
throw new Error("No audio track available. Make sure to share a tab with audio enabled.");
|
|
152
|
+
}
|
|
153
|
+
audioSource = context.createMediaStreamSource(stream);
|
|
154
|
+
} else if (source === "element" && mediaElement) {
|
|
155
|
+
audioSource = context.createMediaElementSource(mediaElement);
|
|
156
|
+
audioSource.connect(context.destination);
|
|
157
|
+
} else {
|
|
158
|
+
throw new Error("Invalid audio source configuration");
|
|
159
|
+
}
|
|
160
|
+
audioSource.connect(analyser);
|
|
161
|
+
audioStateRef.current = {
|
|
162
|
+
context,
|
|
163
|
+
analyser,
|
|
164
|
+
source: audioSource,
|
|
165
|
+
stream
|
|
166
|
+
};
|
|
167
|
+
dataArrayRef.current = new Uint8Array(analyser.frequencyBinCount);
|
|
168
|
+
setState("connected");
|
|
169
|
+
setIsRunning(true);
|
|
170
|
+
animationFrameRef.current = requestAnimationFrame(analyze);
|
|
171
|
+
} catch (err) {
|
|
172
|
+
const audioError = err instanceof Error ? err : new Error(String(err));
|
|
173
|
+
setError(audioError);
|
|
174
|
+
setState("error");
|
|
175
|
+
setIsRunning(false);
|
|
176
|
+
}
|
|
177
|
+
}, [source, mediaElement, fftSize, smoothingTimeConstant, analyze]);
|
|
178
|
+
const stop = useCallback(() => {
|
|
179
|
+
cancelAnimationFrame(animationFrameRef.current);
|
|
180
|
+
const audioState = audioStateRef.current;
|
|
181
|
+
if (audioState) {
|
|
182
|
+
audioState.source.disconnect();
|
|
183
|
+
if (audioState.stream) {
|
|
184
|
+
for (const track of audioState.stream.getTracks()) {
|
|
185
|
+
track.stop();
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
audioState.analyser.disconnect();
|
|
189
|
+
audioState.context.close();
|
|
190
|
+
audioStateRef.current = null;
|
|
191
|
+
}
|
|
192
|
+
dataArrayRef.current = null;
|
|
193
|
+
smoothedLevelsRef.current = { low: 0, mid: 0, high: 0, bands: [...EMPTY_BANDS] };
|
|
194
|
+
setState("disconnected");
|
|
195
|
+
setIsRunning(false);
|
|
196
|
+
setLevels({ low: 0, mid: 0, high: 0, bands: [...EMPTY_BANDS] });
|
|
197
|
+
setFrequencyData(null);
|
|
198
|
+
}, []);
|
|
199
|
+
return {
|
|
200
|
+
levels,
|
|
201
|
+
frequencyData,
|
|
202
|
+
state,
|
|
203
|
+
error,
|
|
204
|
+
start,
|
|
205
|
+
stop,
|
|
206
|
+
isRunning
|
|
207
|
+
};
|
|
208
|
+
}
|
|
1
209
|
// src/ReactShader.tsx
|
|
2
|
-
import { useCallback as
|
|
210
|
+
import { useCallback as useCallback3, useEffect as useEffect2, useMemo, useState as useState2 } from "react";
|
|
3
211
|
|
|
4
212
|
// src/hooks/useWebGL.ts
|
|
5
|
-
import { useCallback, useEffect, useRef } from "react";
|
|
213
|
+
import { useCallback as useCallback2, useEffect, useRef as useRef2 } from "react";
|
|
6
214
|
|
|
7
215
|
// src/utils/shader.ts
|
|
8
216
|
function compileShader(gl, type, source) {
|
|
@@ -367,28 +575,29 @@ function cleanupWebGL(gl, state) {
|
|
|
367
575
|
gl.deleteProgram(state.program);
|
|
368
576
|
}
|
|
369
577
|
function useWebGL(options) {
|
|
370
|
-
const canvasRef =
|
|
371
|
-
const stateRef =
|
|
372
|
-
const animationFrameRef =
|
|
373
|
-
const elapsedTimeRef =
|
|
374
|
-
const lastFrameTimeRef =
|
|
375
|
-
const mouseRef =
|
|
376
|
-
const mouseNormalizedRef =
|
|
377
|
-
const mouseLeftDownRef =
|
|
378
|
-
const canvasRectRef =
|
|
379
|
-
const contextLostRef =
|
|
380
|
-
const uniformsRef =
|
|
381
|
-
const onErrorRef =
|
|
382
|
-
const onFrameRef =
|
|
383
|
-
const onClickRef =
|
|
384
|
-
const onMouseDownRef =
|
|
385
|
-
const onMouseUpRef =
|
|
386
|
-
const onMouseMoveRef =
|
|
387
|
-
const
|
|
388
|
-
const
|
|
389
|
-
const
|
|
390
|
-
const
|
|
391
|
-
const
|
|
578
|
+
const canvasRef = useRef2(null);
|
|
579
|
+
const stateRef = useRef2(null);
|
|
580
|
+
const animationFrameRef = useRef2(0);
|
|
581
|
+
const elapsedTimeRef = useRef2(0);
|
|
582
|
+
const lastFrameTimeRef = useRef2(0);
|
|
583
|
+
const mouseRef = useRef2([0, 0]);
|
|
584
|
+
const mouseNormalizedRef = useRef2([0, 0]);
|
|
585
|
+
const mouseLeftDownRef = useRef2(false);
|
|
586
|
+
const canvasRectRef = useRef2(null);
|
|
587
|
+
const contextLostRef = useRef2(false);
|
|
588
|
+
const uniformsRef = useRef2(options.uniforms);
|
|
589
|
+
const onErrorRef = useRef2(options.onError);
|
|
590
|
+
const onFrameRef = useRef2(options.onFrame);
|
|
591
|
+
const onClickRef = useRef2(options.onClick);
|
|
592
|
+
const onMouseDownRef = useRef2(options.onMouseDown);
|
|
593
|
+
const onMouseUpRef = useRef2(options.onMouseUp);
|
|
594
|
+
const onMouseMoveRef = useRef2(options.onMouseMove);
|
|
595
|
+
const onMouseWheelRef = useRef2(options.onMouseWheel);
|
|
596
|
+
const timeScaleRef = useRef2(options.timeScale ?? 1);
|
|
597
|
+
const vertexRef = useRef2(options.vertex);
|
|
598
|
+
const fragmentRef = useRef2(options.fragment);
|
|
599
|
+
const dprRef = useRef2(window.devicePixelRatio || 1);
|
|
600
|
+
const defaultUniformsRef = useRef2({
|
|
392
601
|
iTime: 0,
|
|
393
602
|
iMouse: [0, 0],
|
|
394
603
|
iMouseNormalized: [0, 0],
|
|
@@ -402,10 +611,11 @@ function useWebGL(options) {
|
|
|
402
611
|
onMouseDownRef.current = options.onMouseDown;
|
|
403
612
|
onMouseUpRef.current = options.onMouseUp;
|
|
404
613
|
onMouseMoveRef.current = options.onMouseMove;
|
|
614
|
+
onMouseWheelRef.current = options.onMouseWheel;
|
|
405
615
|
timeScaleRef.current = options.timeScale ?? 1;
|
|
406
616
|
vertexRef.current = options.vertex;
|
|
407
617
|
fragmentRef.current = options.fragment;
|
|
408
|
-
const render =
|
|
618
|
+
const render = useCallback2((time) => {
|
|
409
619
|
if (contextLostRef.current)
|
|
410
620
|
return;
|
|
411
621
|
const state = stateRef.current;
|
|
@@ -576,10 +786,21 @@ function useWebGL(options) {
|
|
|
576
786
|
mouseLeftDown: mouseLeftDownRef.current
|
|
577
787
|
});
|
|
578
788
|
};
|
|
789
|
+
const handleMouseWheel = (event) => {
|
|
790
|
+
onMouseWheelRef.current?.({
|
|
791
|
+
deltaTime: 0,
|
|
792
|
+
time: elapsedTimeRef.current,
|
|
793
|
+
resolution: [canvas.width, canvas.height],
|
|
794
|
+
mouse: mouseRef.current,
|
|
795
|
+
mouseNormalized: mouseNormalizedRef.current,
|
|
796
|
+
mouseLeftDown: mouseLeftDownRef.current
|
|
797
|
+
}, event.deltaY);
|
|
798
|
+
};
|
|
579
799
|
window.addEventListener("mousemove", handleMouseMove);
|
|
580
800
|
window.addEventListener("mousedown", handleMouseDown);
|
|
581
801
|
window.addEventListener("mouseup", handleMouseUp);
|
|
582
802
|
canvas.addEventListener("click", handleClick);
|
|
803
|
+
window.addEventListener("wheel", handleMouseWheel);
|
|
583
804
|
return () => {
|
|
584
805
|
resizeObserver.disconnect();
|
|
585
806
|
window.removeEventListener("scroll", updateRect);
|
|
@@ -587,6 +808,7 @@ function useWebGL(options) {
|
|
|
587
808
|
window.removeEventListener("mousedown", handleMouseDown);
|
|
588
809
|
window.removeEventListener("mouseup", handleMouseUp);
|
|
589
810
|
canvas.removeEventListener("click", handleClick);
|
|
811
|
+
window.removeEventListener("wheel", handleMouseWheel);
|
|
590
812
|
};
|
|
591
813
|
}, []);
|
|
592
814
|
return { canvasRef, mouseRef };
|
|
@@ -595,6 +817,7 @@ function useWebGL(options) {
|
|
|
595
817
|
// src/ReactShader.tsx
|
|
596
818
|
import { jsxDEV } from "react/jsx-dev-runtime";
|
|
597
819
|
var DEFAULT_VERTEX = `#version 300 es
|
|
820
|
+
precision highp float;
|
|
598
821
|
in vec2 a_position;
|
|
599
822
|
|
|
600
823
|
void main() {
|
|
@@ -630,10 +853,11 @@ function ReactShader({
|
|
|
630
853
|
onClick,
|
|
631
854
|
onMouseMove,
|
|
632
855
|
onMouseDown,
|
|
633
|
-
onMouseUp
|
|
856
|
+
onMouseUp,
|
|
857
|
+
onMouseWheel
|
|
634
858
|
}) {
|
|
635
|
-
const [error, setError] =
|
|
636
|
-
const handleError =
|
|
859
|
+
const [error, setError] = useState2(null);
|
|
860
|
+
const handleError = useCallback3((err) => {
|
|
637
861
|
setError(err.message);
|
|
638
862
|
console.error("ReactShader error:", err);
|
|
639
863
|
}, []);
|
|
@@ -650,6 +874,7 @@ function ReactShader({
|
|
|
650
874
|
onMouseMove,
|
|
651
875
|
onMouseDown,
|
|
652
876
|
onMouseUp,
|
|
877
|
+
onMouseWheel,
|
|
653
878
|
timeScale
|
|
654
879
|
});
|
|
655
880
|
const containerStyle = useMemo(() => fullscreen ? FULLSCREEN_CONTAINER_STYLE : DEFAULT_CONTAINER_STYLE, [fullscreen]);
|
|
@@ -950,6 +1175,7 @@ function generateUtilsFunction() {
|
|
|
950
1175
|
`;
|
|
951
1176
|
}
|
|
952
1177
|
export {
|
|
1178
|
+
useAudio,
|
|
953
1179
|
generateUtilsFunction,
|
|
954
1180
|
generateSimplexNoiseFunction,
|
|
955
1181
|
generateSceneCirclesFunction,
|
package/dist/types.d.ts
CHANGED
|
@@ -38,6 +38,7 @@ export interface ReactShaderProps {
|
|
|
38
38
|
onMouseMove?: (info: FrameInfo) => void;
|
|
39
39
|
onMouseDown?: (info: FrameInfo) => void;
|
|
40
40
|
onMouseUp?: (info: FrameInfo) => void;
|
|
41
|
+
onMouseWheel?: (info: FrameInfo, wheelDelta: number) => void;
|
|
41
42
|
}
|
|
42
43
|
export interface DefaultUniforms {
|
|
43
44
|
iTime: number;
|
|
@@ -46,4 +47,33 @@ export interface DefaultUniforms {
|
|
|
46
47
|
iMouseLeftDown: number;
|
|
47
48
|
iResolution: Vec2;
|
|
48
49
|
}
|
|
50
|
+
export interface AudioLevels {
|
|
51
|
+
low: number;
|
|
52
|
+
mid: number;
|
|
53
|
+
high: number;
|
|
54
|
+
bands: number[];
|
|
55
|
+
}
|
|
56
|
+
export type AudioSourceType = "microphone" | "element" | "display";
|
|
57
|
+
export type AudioConnectionState = "disconnected" | "connecting" | "connected" | "error";
|
|
58
|
+
export interface UseAudioOptions {
|
|
59
|
+
source?: AudioSourceType;
|
|
60
|
+
mediaElement?: HTMLAudioElement | HTMLVideoElement | null;
|
|
61
|
+
fftSize?: number;
|
|
62
|
+
smoothingTimeConstant?: number;
|
|
63
|
+
smoothing?: number;
|
|
64
|
+
frequencyBands?: Partial<{
|
|
65
|
+
low: [number, number];
|
|
66
|
+
mid: [number, number];
|
|
67
|
+
high: [number, number];
|
|
68
|
+
}>;
|
|
69
|
+
}
|
|
70
|
+
export interface UseAudioReturn {
|
|
71
|
+
levels: AudioLevels;
|
|
72
|
+
frequencyData: Uint8Array<ArrayBuffer> | null;
|
|
73
|
+
state: AudioConnectionState;
|
|
74
|
+
error: Error | null;
|
|
75
|
+
start: () => Promise<void>;
|
|
76
|
+
stop: () => void;
|
|
77
|
+
isRunning: boolean;
|
|
78
|
+
}
|
|
49
79
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;AACnC,MAAM,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;AAC3C,MAAM,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;AAEnD,MAAM,MAAM,UAAU,GAAG,MAAM,EAAE,CAAA;AACjC,MAAM,MAAM,SAAS,GAAG,IAAI,EAAE,CAAA;AAC9B,MAAM,MAAM,SAAS,GAAG,IAAI,EAAE,CAAA;AAC9B,MAAM,MAAM,SAAS,GAAG,IAAI,EAAE,CAAA;AAE9B,MAAM,MAAM,aAAa,GACrB,gBAAgB,GAChB,iBAAiB,GACjB,gBAAgB,GAChB,WAAW,GACX,SAAS,GACT,eAAe,CAAA;AAEnB,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAA;AACvD,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,CAAA;AAC9D,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,QAAQ,CAAA;AAEnD,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,aAAa,CAAA;IACrB,KAAK,CAAC,EAAE,WAAW,CAAA;IACnB,KAAK,CAAC,EAAE,WAAW,CAAA;IACnB,SAAS,CAAC,EAAE,gBAAgB,CAAA;IAC5B,SAAS,CAAC,EAAE,gBAAgB,CAAA;IAC5B,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,MAAM,MAAM,YAAY,GACpB,MAAM,GACN,IAAI,GACJ,IAAI,GACJ,IAAI,GACJ,UAAU,GACV,SAAS,GACT,SAAS,GACT,SAAS,GACT,aAAa,GACb,cAAc,CAAA;AAElB,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC5B,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACvB,eAAe,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACjC,aAAa,EAAE,OAAO,CAAA;CACvB;AACD,MAAM,WAAW,gBAAgB;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;IACvC,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAA;IACnC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAA;IACnC,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAA;IACvC,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAA;IACvC,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|