Skip to content

Commit 96fd5c7

Browse files
authored
Merge pull request #3 from azan-n/azan/webchuck
chuck chuck chuck
2 parents 9271d56 + 53e1a33 commit 96fd5c7

8 files changed

Lines changed: 152 additions & 775 deletions
-2.3 MB
Binary file not shown.
-7.9 MB
Binary file not shown.
-2.65 MB
Binary file not shown.

package-lock.json

Lines changed: 69 additions & 696 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,14 @@
1010
"preview": "vite preview"
1111
},
1212
"dependencies": {
13-
"colorthief": "^2.6.0",
13+
"bezier-js": "^6.1.4",
1414
"react": "^18.3.1",
1515
"react-dom": "^18.3.1",
16-
"webchuck": "^1.2.10"
16+
"tone": "^15.0.4"
1717
},
1818
"devDependencies": {
1919
"@eslint/js": "^9.17.0",
20+
"@types/bezier-js": "^4.1.3",
2021
"@types/react": "^18.3.18",
2122
"@types/react-dom": "^18.3.5",
2223
"@vitejs/plugin-react": "^4.3.4",

src/App.tsx

Lines changed: 2 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,13 @@
1-
import { useState } from "react"
2-
import ColorThief, { RGBColor } from 'colorthief';
31
import BezierCurve from "./components/BezierCurve";
42

53
function App() {
6-
const [image, setImage] = useState<string | null>(null)
7-
const [dominantColors, setDominantColors] = useState<RGBColor[]>([])
8-
// new Bezier().
9-
// Handle image upload
10-
const handleImageUpload = (event: any) => {
11-
const file = event.target.files[0]
12-
if (file) {
13-
const imgFile = URL.createObjectURL(file)
14-
setImage(imgFile)
15-
extractDominantColors(file)
16-
}
17-
}
18-
19-
const extractDominantColors = (file: Blob | MediaSource) => {
20-
const img = new Image()
21-
img.src = URL.createObjectURL(file)
22-
img.onload = () => {
23-
const colorThief = new ColorThief()
24-
const colors = colorThief.getPalette(img)
25-
if (!colors) {
26-
throw Error('Palette generation failed.')
27-
} else {
28-
setDominantColors(colors)
29-
console.log(dominantColors);
30-
}
31-
}
32-
}
33-
344
return (
355
<main className='h-screen bg-slate-900 w-full text-slate-400 border-slate-700'>
366
<div className='p-4'>
377
<h1 className='font-mono'>scurvee</h1>
38-
<section className='pt-4 grid grid-cols-1 md:grid-cols-2 gap-4 h-[calc(100vh_-_4rem)]'>
39-
{/* Upload image, show image, remove image */}
40-
<div className='border overflow-hidden flex items-center justify-center'>
41-
{!image ?
42-
(<label htmlFor="file-upload" className='cursor-pointer'>
43-
<input
44-
id="file-upload"
45-
type="file"
46-
accept="image/*"
47-
onChange={handleImageUpload}
48-
className='inset-0 opacity-0' // Hide the default input
49-
/>
50-
<div className='bg-slate-700 cursor-pointer text-slate-200 py-3 px-6 rounded-lg hover:bg-slate-600 transition duration-300'>
51-
Upload Image
52-
</div>
53-
</label>) : null}
54-
55-
56-
{image ? (
57-
<img className="object-cover w-full h-full" src={image} alt="" />)
58-
: (
59-
null
60-
)}
61-
</div>
62-
8+
<section className='pt-4 h-[calc(100vh_-_4rem)]'>
639
{/* MIDI visualizations? */}
64-
<div className='border'>
10+
<div className='border h-full'>
6511
<BezierCurve></BezierCurve>
6612
</div>
6713
</section>

src/color-thief.d.ts

Lines changed: 0 additions & 16 deletions
This file was deleted.

src/components/BezierCurve.tsx

Lines changed: 78 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import { useState } from "react";
1+
import { Bezier } from "bezier-js";
2+
import { useEffect, useState } from "react";
3+
import * as Tone from "tone";
24

35
interface Point {
46
x: number;
@@ -15,6 +17,10 @@ const BezierCurve: React.FC = () => {
1517
{ x: 790, y: 500 },
1618
]);
1719

20+
const generateBezier = () => {
21+
return new Bezier(controlPoints);
22+
}
23+
1824
const [dragging, setDragging] = useState<number | null>(null); // Index of the point being dragged
1925
const [dragOffset, setDragOffset] = useState<[number, number] | null>(null); // Offset of mouse from control point
2026

@@ -63,10 +69,17 @@ const BezierCurve: React.FC = () => {
6369
setDragging(null); // Stop dragging
6470
};
6571

72+
const [bezier, setBezier] = useState<Bezier>(generateBezier());
73+
74+
useEffect(() => {
75+
setBezier(generateBezier())
76+
}, [controlPoints])
77+
6678
return (
67-
<div>
79+
<>
80+
6881
<svg
69-
className=""
82+
className="h-full w-full"
7083
viewBox="0 0 800 600"
7184
onMouseMove={onMouseMove}
7285
onMouseUp={onMouseUp}
@@ -105,9 +118,69 @@ const BezierCurve: React.FC = () => {
105118
);
106119
})}
107120
</svg>
108-
<p>{JSON.stringify(controlPoints)}</p>
109-
</div>
121+
<MidiNotesPlayer bezier={bezier} />
122+
</>
110123
);
111124
};
112125

113126
export default BezierCurve;
127+
128+
import React from 'react';
129+
130+
export function MidiNotesPlayer({ bezier }: { bezier: Bezier }) {
131+
const [isPlaying, setIsPlaying] = useState(false);
132+
const [loop, setLoop] = useState<Tone.Loop>();
133+
const [synth, setSynth] = useState<Tone.Synth>();
134+
135+
// Function to play the notes using Tone.js
136+
const playNotes = () => {
137+
Tone.start().then(() => {
138+
const synth = new Tone.Synth().toDestination();
139+
setSynth(synth);
140+
141+
const loop = new Tone.Loop(getLoopCallback(bezier, synth), "4n");
142+
loop.start()
143+
144+
setLoop(loop)
145+
// all loops start when the Transport is started
146+
Tone.getTransport().start()
147+
setIsPlaying(true); // Toggle the play/pause state
148+
});
149+
};
150+
151+
useEffect(() => {
152+
if (loop && isPlaying && synth) {
153+
loop.callback = getLoopCallback(bezier, synth);
154+
}
155+
}, [bezier])
156+
157+
const togglePlay = async () => {
158+
if (!isPlaying) {
159+
playNotes();
160+
} else {
161+
synth?.dispose()
162+
loop?.dispose()
163+
Tone.getTransport().stop();
164+
setIsPlaying(false);
165+
}
166+
};
167+
168+
return (
169+
<div>
170+
<button className="font-mono" onClick={togglePlay}>
171+
{isPlaying ? 'stop' : 'start'}
172+
</button>
173+
</div>
174+
);
175+
};
176+
177+
function getLoopCallback(bezier: Bezier, synth: Tone.Synth<Tone.SynthOptions>): ((time: Tone.Unit.Seconds) => void) {
178+
return (time) => {
179+
const lut = bezier.getLUT(4);
180+
synth?.triggerAttackRelease(lut[randomInt(0, lut.length - 1)].y, "8n", time);
181+
};
182+
}
183+
184+
function randomInt(min: number, max: number) { // min and max included
185+
return Math.floor(Math.random() * (max - min + 1) + min);
186+
}

0 commit comments

Comments
 (0)