Get infinite scrolling and basic audio working
This commit is contained in:
parent
1c575a84de
commit
5a821ff43a
|
@ -1,11 +1,10 @@
|
||||||
import * as THREE from "three";
|
import * as THREE from "three";
|
||||||
import React, { useRef, Suspense } from "react";
|
import React, { Suspense } from "react";
|
||||||
import { useLoader, useFrame } from "@react-three/fiber";
|
import { useLoader } from "@react-three/fiber";
|
||||||
|
|
||||||
interface ImagePlaneProps {
|
interface ImagePlaneProps {
|
||||||
url: string;
|
url: string;
|
||||||
height: number;
|
height: number;
|
||||||
paused: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -14,20 +13,10 @@ interface ImagePlaneProps {
|
||||||
export default function ImagePlane(props: ImagePlaneProps) {
|
export default function ImagePlane(props: ImagePlaneProps) {
|
||||||
const texture = useLoader(THREE.TextureLoader, props.url);
|
const texture = useLoader(THREE.TextureLoader, props.url);
|
||||||
|
|
||||||
const mesh = useRef<THREE.Mesh>(null);
|
|
||||||
|
|
||||||
useFrame(() => {
|
|
||||||
if (props.paused) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mesh.current.position.y += 1 / 30.0;
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Suspense fallback={null}>
|
<Suspense fallback={null}>
|
||||||
<mesh ref={mesh} rotation-z={-Math.PI / 2} position-y={props.height}>
|
<mesh rotation-z={-Math.PI / 2} position-y={props.height}>
|
||||||
<planeBufferGeometry attach="geometry" args={[30, 30]} />
|
<planeGeometry attach="geometry" args={[5, 5]} />
|
||||||
<meshBasicMaterial attach="material" map={texture} />
|
<meshBasicMaterial attach="material" map={texture} />
|
||||||
</mesh>
|
</mesh>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
|
|
@ -27,7 +27,7 @@ export default function RotatingBox(props: JSX.IntrinsicElements["mesh"]) {
|
||||||
smoothness={4}
|
smoothness={4}
|
||||||
{...props}
|
{...props}
|
||||||
ref={mesh}
|
ref={mesh}
|
||||||
scale={active ? [3, 3, 3] : [2, 2, 2]}
|
scale={active ? [.3, .3, .3] : [.2, .2, .2]}
|
||||||
onClick={() => setActive(!active)}
|
onClick={() => setActive(!active)}
|
||||||
onPointerOver={() => setHover(true)}
|
onPointerOver={() => setHover(true)}
|
||||||
onPointerOut={() => setHover(false)}
|
onPointerOut={() => setHover(false)}
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
import ImagePlane from "./ImagePlane";
|
||||||
|
|
||||||
|
import { useRef } from "react";
|
||||||
|
import { useFrame, useThree } from "@react-three/fiber";
|
||||||
|
|
||||||
|
import { InferenceResult } from "../types";
|
||||||
|
import { QuadraticBezierLine } from "@react-three/drei";
|
||||||
|
|
||||||
|
interface SpectrogramViewerProps {
|
||||||
|
paused: boolean;
|
||||||
|
inferenceResults: InferenceResult[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spectrogram drawing code.
|
||||||
|
*/
|
||||||
|
export default function SpectrogramViewer(props: SpectrogramViewerProps) {
|
||||||
|
const paused = props.paused;
|
||||||
|
const inferenceResults = props.inferenceResults;
|
||||||
|
|
||||||
|
const camera = useThree((state) => state.camera);
|
||||||
|
|
||||||
|
const playheadRef = useRef(null);
|
||||||
|
|
||||||
|
// Move the camera and playhead every frame.
|
||||||
|
useFrame(() => {
|
||||||
|
if (paused) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const framerate = 120.0;
|
||||||
|
camera.position.y -= 1 / framerate;
|
||||||
|
playheadRef.current.position.y -= 1 / framerate;
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<group>
|
||||||
|
{inferenceResults.map((value: InferenceResult, index: number) => {
|
||||||
|
const height = 5 * (-1 - value.counter) - 2;
|
||||||
|
return <ImagePlane url={value.image} height={height} key={index} />;
|
||||||
|
})}
|
||||||
|
|
||||||
|
{/* TODO make into playhead component */}
|
||||||
|
<group ref={playheadRef}>
|
||||||
|
<QuadraticBezierLine
|
||||||
|
start={[-3, 0, 1]} // Starting point, can be an array or a vec3
|
||||||
|
end={[3, 0, 1]} // Ending point, can be an array or a vec3
|
||||||
|
mid={[0, -0.8, 0.4]} // Optional control point, can be an array or a vec3
|
||||||
|
color="#aa3333" // Default
|
||||||
|
lineWidth={5} // In pixels (default)
|
||||||
|
dashed={false} // Default
|
||||||
|
/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,34 +1,25 @@
|
||||||
import { Canvas } from "@react-three/fiber";
|
import { Canvas } from "@react-three/fiber";
|
||||||
|
|
||||||
import RotatingBox from "./RotatingBox";
|
import { InferenceResult } from "../types";
|
||||||
import ImagePlane from "./ImagePlane";
|
import SpectrogramViewer from "./SpectrogramViewer";
|
||||||
|
|
||||||
interface CanvasProps {
|
interface CanvasProps {
|
||||||
paused: boolean;
|
paused: boolean;
|
||||||
|
inferenceResults: InferenceResult[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* React three fiber canvas with spectrogram drawing.
|
* React three fiber canvas with spectrogram drawing.
|
||||||
*/
|
*/
|
||||||
export default function ThreeCanvas(props: CanvasProps) {
|
export default function ThreeCanvas(props: CanvasProps) {
|
||||||
// change the image URL
|
|
||||||
const spectrogram_image = "spectrogram.jpeg";
|
|
||||||
|
|
||||||
const height = -30.0;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Canvas camera={{ position: [0, 0, 35], rotation: [0.2, 0, 0] }}>
|
<Canvas camera={{ position: [0, 0, 7], rotation: [0.2, 0, 0] }}>
|
||||||
<ambientLight intensity={2} />
|
<ambientLight intensity={2} />
|
||||||
<pointLight position={[40, 40, 40]} />
|
<pointLight position={[40, 40, 40]} />
|
||||||
<ImagePlane
|
<SpectrogramViewer
|
||||||
url={spectrogram_image}
|
|
||||||
height={height}
|
|
||||||
paused={props.paused}
|
paused={props.paused}
|
||||||
|
inferenceResults={props.inferenceResults}
|
||||||
/>
|
/>
|
||||||
<RotatingBox position={[-12, 0, 1]} />
|
|
||||||
<RotatingBox position={[-4, 0, 1]} />
|
|
||||||
<RotatingBox position={[4, 0, 1]} />
|
|
||||||
<RotatingBox position={[12, 0, 1]} />
|
|
||||||
</Canvas>
|
</Canvas>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,8 @@
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-icons": "^4.6.0",
|
"react-icons": "^4.6.0",
|
||||||
"three": "^0.146.0"
|
"three": "^0.146.0",
|
||||||
|
"tone": "^14.7.77"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^18.11.9",
|
"@types/node": "^18.11.9",
|
||||||
|
@ -938,6 +939,18 @@
|
||||||
"resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz",
|
||||||
"integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag=="
|
"integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag=="
|
||||||
},
|
},
|
||||||
|
"node_modules/automation-events": {
|
||||||
|
"version": "4.0.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/automation-events/-/automation-events-4.0.24.tgz",
|
||||||
|
"integrity": "sha512-v/c//VCE1TGU3R56cyTFnebXFR3NOOx5Kiuaz7TXwLGpYMp9VgKCwelODRW3ZZ5TP2OFymfrWsvNVuPSi6vVPQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.20.1",
|
||||||
|
"tslib": "^2.4.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.20.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/autoprefixer": {
|
"node_modules/autoprefixer": {
|
||||||
"version": "10.4.13",
|
"version": "10.4.13",
|
||||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.13.tgz",
|
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.13.tgz",
|
||||||
|
@ -3642,6 +3655,16 @@
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/standardized-audio-context": {
|
||||||
|
"version": "25.3.35",
|
||||||
|
"resolved": "https://registry.npmjs.org/standardized-audio-context/-/standardized-audio-context-25.3.35.tgz",
|
||||||
|
"integrity": "sha512-61KBkor9fOsMEl3NZllnis8KDmTJzonuuW8h6JBmxXXcUDucypGCJp+hlbyKG+olav8wgA+VKlqFmiJEbh4CfA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.20.1",
|
||||||
|
"automation-events": "^4.0.24",
|
||||||
|
"tslib": "^2.4.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/stats.js": {
|
"node_modules/stats.js": {
|
||||||
"version": "0.17.0",
|
"version": "0.17.0",
|
||||||
"resolved": "https://registry.npmjs.org/stats.js/-/stats.js-0.17.0.tgz",
|
"resolved": "https://registry.npmjs.org/stats.js/-/stats.js-0.17.0.tgz",
|
||||||
|
@ -3906,6 +3929,15 @@
|
||||||
"node": ">=8.0"
|
"node": ">=8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tone": {
|
||||||
|
"version": "14.7.77",
|
||||||
|
"resolved": "https://registry.npmjs.org/tone/-/tone-14.7.77.tgz",
|
||||||
|
"integrity": "sha512-tCfK73IkLHyzoKUvGq47gyDyxiKLFvKiVCOobynGgBB9Dl0NkxTM2p+eRJXyCYrjJwy9Y0XCMqD3uOYsYt2Fdg==",
|
||||||
|
"dependencies": {
|
||||||
|
"standardized-audio-context": "^25.1.8",
|
||||||
|
"tslib": "^2.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/troika-three-text": {
|
"node_modules/troika-three-text": {
|
||||||
"version": "0.46.4",
|
"version": "0.46.4",
|
||||||
"resolved": "https://registry.npmjs.org/troika-three-text/-/troika-three-text-0.46.4.tgz",
|
"resolved": "https://registry.npmjs.org/troika-three-text/-/troika-three-text-0.46.4.tgz",
|
||||||
|
@ -4770,6 +4802,15 @@
|
||||||
"resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz",
|
||||||
"integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag=="
|
"integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag=="
|
||||||
},
|
},
|
||||||
|
"automation-events": {
|
||||||
|
"version": "4.0.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/automation-events/-/automation-events-4.0.24.tgz",
|
||||||
|
"integrity": "sha512-v/c//VCE1TGU3R56cyTFnebXFR3NOOx5Kiuaz7TXwLGpYMp9VgKCwelODRW3ZZ5TP2OFymfrWsvNVuPSi6vVPQ==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.20.1",
|
||||||
|
"tslib": "^2.4.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"autoprefixer": {
|
"autoprefixer": {
|
||||||
"version": "10.4.13",
|
"version": "10.4.13",
|
||||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.13.tgz",
|
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.13.tgz",
|
||||||
|
@ -6645,6 +6686,16 @@
|
||||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
|
||||||
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw=="
|
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw=="
|
||||||
},
|
},
|
||||||
|
"standardized-audio-context": {
|
||||||
|
"version": "25.3.35",
|
||||||
|
"resolved": "https://registry.npmjs.org/standardized-audio-context/-/standardized-audio-context-25.3.35.tgz",
|
||||||
|
"integrity": "sha512-61KBkor9fOsMEl3NZllnis8KDmTJzonuuW8h6JBmxXXcUDucypGCJp+hlbyKG+olav8wgA+VKlqFmiJEbh4CfA==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.20.1",
|
||||||
|
"automation-events": "^4.0.24",
|
||||||
|
"tslib": "^2.4.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"stats.js": {
|
"stats.js": {
|
||||||
"version": "0.17.0",
|
"version": "0.17.0",
|
||||||
"resolved": "https://registry.npmjs.org/stats.js/-/stats.js-0.17.0.tgz",
|
"resolved": "https://registry.npmjs.org/stats.js/-/stats.js-0.17.0.tgz",
|
||||||
|
@ -6836,6 +6887,15 @@
|
||||||
"is-number": "^7.0.0"
|
"is-number": "^7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"tone": {
|
||||||
|
"version": "14.7.77",
|
||||||
|
"resolved": "https://registry.npmjs.org/tone/-/tone-14.7.77.tgz",
|
||||||
|
"integrity": "sha512-tCfK73IkLHyzoKUvGq47gyDyxiKLFvKiVCOobynGgBB9Dl0NkxTM2p+eRJXyCYrjJwy9Y0XCMqD3uOYsYt2Fdg==",
|
||||||
|
"requires": {
|
||||||
|
"standardized-audio-context": "^25.1.8",
|
||||||
|
"tslib": "^2.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"troika-three-text": {
|
"troika-three-text": {
|
||||||
"version": "0.46.4",
|
"version": "0.46.4",
|
||||||
"resolved": "https://registry.npmjs.org/troika-three-text/-/troika-three-text-0.46.4.tgz",
|
"resolved": "https://registry.npmjs.org/troika-three-text/-/troika-three-text-0.46.4.tgz",
|
||||||
|
|
|
@ -9,16 +9,17 @@
|
||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@headlessui/react": "^1.5.0",
|
||||||
"@react-three/drei": "^9.41.2",
|
"@react-three/drei": "^9.41.2",
|
||||||
"@react-three/fiber": "^8.9.1",
|
"@react-three/fiber": "^8.9.1",
|
||||||
"eslint": "8.27.0",
|
"eslint": "8.27.0",
|
||||||
"eslint-config-next": "13.0.4",
|
"eslint-config-next": "13.0.4",
|
||||||
"@headlessui/react": "^1.5.0",
|
|
||||||
"next": "13.0.4",
|
"next": "13.0.4",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-icons": "^4.6.0",
|
"react-icons": "^4.6.0",
|
||||||
"three": "^0.146.0"
|
"three": "^0.146.0",
|
||||||
|
"tone": "^14.7.77"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^18.11.9",
|
"@types/node": "^18.11.9",
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
import { useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
import ThreeCanvas from "../components/ThreeCanvas";
|
import ThreeCanvas from "../components/ThreeCanvas";
|
||||||
import PromptPanel from "../components/PromptPanel";
|
import PromptPanel from "../components/PromptPanel";
|
||||||
|
@ -8,17 +8,81 @@ import Pause from "../components/Pause";
|
||||||
|
|
||||||
import { InferenceResult, PromptInput } from "../types";
|
import { InferenceResult, PromptInput } from "../types";
|
||||||
|
|
||||||
|
import * as Tone from "tone";
|
||||||
|
|
||||||
const defaultPromptInputs = [
|
const defaultPromptInputs = [
|
||||||
{ prompt: "A jazz pianist playing a classical concerto", seed: 10 },
|
{ prompt: "A jazz pianist playing a classical concerto", seed: 10 },
|
||||||
{ prompt: "Taylor Swift singing with a tropical beat", seed: 10 },
|
{ prompt: "Taylor Swift singing with a tropical beat", seed: 10 },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const defaultInferenceResults = [
|
||||||
|
{
|
||||||
|
input: {
|
||||||
|
alpha: 0.0,
|
||||||
|
start: defaultPromptInputs[0],
|
||||||
|
end: defaultPromptInputs[1],
|
||||||
|
},
|
||||||
|
image: "rap_sample.jpg",
|
||||||
|
audio: "rap_sample.mp3",
|
||||||
|
counter: 0,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const timeout = 5000;
|
||||||
|
const maxLength = 10;
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const [paused, setPaused] = useState(false);
|
const [paused, setPaused] = useState(false);
|
||||||
|
|
||||||
const [promptInputs, setPromptInputs] =
|
const [promptInputs, setPromptInputs] =
|
||||||
useState<PromptInput[]>(defaultPromptInputs);
|
useState<PromptInput[]>(defaultPromptInputs);
|
||||||
|
|
||||||
|
const [inferenceResults, setInferenceResults] = useState<InferenceResult[]>(
|
||||||
|
defaultInferenceResults
|
||||||
|
);
|
||||||
|
|
||||||
|
// /////////////
|
||||||
|
|
||||||
|
const [tonePlayer, setTonePlayer] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTonePlayer(
|
||||||
|
new Tone.Player(defaultInferenceResults[0].audio).toDestination()
|
||||||
|
);
|
||||||
|
// play as soon as the buffer is loaded
|
||||||
|
// player.autostart = true;
|
||||||
|
}, [inferenceResults]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (tonePlayer && tonePlayer.loaded) {
|
||||||
|
if (!paused) {
|
||||||
|
tonePlayer.start();
|
||||||
|
} else {
|
||||||
|
tonePlayer.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [paused, tonePlayer]);
|
||||||
|
|
||||||
|
// /////////////
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
const lastResult = inferenceResults[inferenceResults.length - 1];
|
||||||
|
const newResult = { ...lastResult, counter: lastResult.counter + 1 };
|
||||||
|
|
||||||
|
let results = [...inferenceResults, newResult];
|
||||||
|
|
||||||
|
if (results.length > maxLength) {
|
||||||
|
results = results.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Adding to inference results");
|
||||||
|
console.log(results);
|
||||||
|
|
||||||
|
setInferenceResults(results);
|
||||||
|
}, timeout);
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
|
@ -32,7 +96,7 @@ export default function Home() {
|
||||||
|
|
||||||
<div className="bg-sky-900 flex flex-row min-h-screen text-white">
|
<div className="bg-sky-900 flex flex-row min-h-screen text-white">
|
||||||
<div className="w-1/3 min-h-screen">
|
<div className="w-1/3 min-h-screen">
|
||||||
<ThreeCanvas paused={paused} />
|
<ThreeCanvas paused={paused} inferenceResults={inferenceResults} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<PromptPanel
|
<PromptPanel
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 208 KiB |
Binary file not shown.
Loading…
Reference in New Issue