diff --git a/app/audio/page.tsx b/app/audio/page.tsx index c41f2ec..dbd4bde 100644 --- a/app/audio/page.tsx +++ b/app/audio/page.tsx @@ -63,7 +63,7 @@ export default function Page() { return (
{!audioStorage && } diff --git a/app/layout.tsx b/app/layout.tsx index 94b1d90..90b2491 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -47,7 +47,7 @@ export default function RootLayout({ {/* */}
{children}
diff --git a/components/PanKnob.tsx b/components/PanKnob.tsx index a5da47b..bc9f39d 100644 --- a/components/PanKnob.tsx +++ b/components/PanKnob.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useRef, useState, useEffect, PointerEventHandler } from "react"; import { Knob, Pointer } from "rc-knob"; import { CustomArc } from "./CustomArc"; @@ -12,13 +12,104 @@ const PanKnob = ({ value, setValue }: Props) => { const max = 100; const size = 40; const arcWidth = size * 0.15; - const pointerSize = size * 0.08; - const arcRadius = size / 2 - arcWidth / 2; + const pointerSize = size * 0.08; const pointerRadius = arcRadius - pointerSize; + const knobRef = useRef(null); + const currentVal = useRef(value); + const [isDragging, setIsDragging] = useState(false); + const totalAngle = useRef(0); + const lastAngle = useRef(0); + const [startValue, setStartValue] = useState(value); + + // Function to handle pointer down + const handlePointerDown: PointerEventHandler = (e) => { + if (!knobRef.current) return; + + const rect = knobRef.current.getBoundingClientRect(); + const center = { + x: rect.left + rect.width / 2, + y: rect.top + rect.height / 2, + }; + + const angle = Math.atan2(e.clientY - center.y, e.clientX - center.x); + + lastAngle.current = angle; + totalAngle.current = 0; + setStartValue(value); + setIsDragging(true); + e.preventDefault(); + }; + + // Function to handle pointer move and calculate value + const handlePointerMove = (e: PointerEvent) => { + if (!isDragging || !knobRef.current) return; + + const rect = knobRef.current.getBoundingClientRect(); + const center = { + x: rect.left + rect.width / 2, + y: rect.top + rect.height / 2, + }; + + const angle = Math.atan2(e.clientY - center.y, e.clientX - center.x); + let deltaAngle = angle - lastAngle.current; + + // Adjust for angle wrapping + if (deltaAngle > Math.PI) { + deltaAngle -= 2 * Math.PI; + } else if (deltaAngle < -Math.PI) { + deltaAngle += 2 * Math.PI; + } + + totalAngle.current += deltaAngle; + lastAngle.current = angle; + + // Map totalAngle to value + const totalAngleRangeRadians = (280 * Math.PI) / 180; // 280 degrees in radians + const valueRange = max - min; // 200 + let newValue = + startValue + (totalAngle.current / totalAngleRangeRadians) * valueRange; + + let finalValue = Math.round(newValue / 10) * 10; + + // Format Final Value + if (finalValue < min) finalValue = min; + else if (finalValue > max) finalValue = max; + if (Math.abs(currentVal.current - finalValue) > 50) return; + + setValue(finalValue); + }; + + // Function to handle pointer up + const handlePointerUp = () => setIsDragging(false); + + // Manage adding and removing pointer event listeners + useEffect(() => { + if (isDragging) { + window.addEventListener("pointermove", handlePointerMove); + window.addEventListener("pointerup", handlePointerUp); + } else { + window.removeEventListener("pointermove", handlePointerMove); + window.removeEventListener("pointerup", handlePointerUp); + } + return () => { + window.removeEventListener("pointermove", handlePointerMove); + window.removeEventListener("pointerup", handlePointerUp); + }; + }, [isDragging]); + + useEffect(() => { + if (value === currentVal.current) return; + currentVal.current = value; + }, [value]); + return ( -
+
{ steps={10} value={value} onChange={(val) => { - if (val == value) return; if (Math.abs(value - val) > 50) return; setTimeout(() => setValue(Math.round(val / 10) * 10), 10); }} @@ -88,7 +178,9 @@ const PanKnob = ({ value, setValue }: Props) => { L

{value.toFixed(0)}

diff --git a/components/PianoRoll.tsx b/components/PianoRoll.tsx index f72a406..381ef98 100644 --- a/components/PianoRoll.tsx +++ b/components/PianoRoll.tsx @@ -1,10 +1,12 @@ "use client"; -import synthInit from "@/utils/synthInit"; -import { useTheme } from "next-themes"; import React, { useEffect, useRef, useState } from "react"; -import * as Tone from "tone"; +import synthInit from "@/utils/synthInit"; import { Midi } from "tonejs-midi-fix"; +import * as Tone from "tone"; +import { Button } from "@/components/ui/button"; +import { useTheme } from "next-themes"; +import { PauseCircle, PlayCircle } from "lucide-react"; interface PianoRollProps { title: string; @@ -52,11 +54,10 @@ const PianoRoll: React.FC = ({ // Constants const pianoKeyWidth = 50; - const containerWidth = 600 - pianoKeyWidth; - const noteHeight = 7; + const containerWidth = parentRef.current?.clientWidth || 800 - pianoKeyWidth; + const noteHeight = containerWidth > 600 ? 7 : containerWidth > 300 ? 6 : 5; const totalNotes = 88; const canvasHeight = totalNotes * noteHeight; - const containerHeight = 400; // Helper function to compare two sets const areSetsEqual = (a: Set, b: Set): boolean => { @@ -155,11 +156,12 @@ const PianoRoll: React.FC = ({ const height = canvas.height; const secondsPerBeat = 60 / tempo.current; - const eightMeasures = 8 * 4 * secondsPerBeat; - const timeScale = 950 / eightMeasures; // Pixels Per Second - canvas.width = duration * timeScale + 950; + const measures = containerWidth > 600 ? 8 : containerWidth > 300 ? 6 : 4; + const viewLength = measures * 4 * secondsPerBeat; + const timeScale = containerWidth / viewLength; // Pixels Per Second + canvas.width = duration * timeScale + containerWidth - pianoKeyWidth; - ctx.clearRect(0, 0, 950, height); + ctx.clearRect(0, 0, containerWidth, height); // Draw each note as a rectangle notes.forEach((note) => { @@ -319,17 +321,11 @@ const PianoRoll: React.FC = ({ ref={parentRef} className="relative flex h-[300px] rounded-2xl bg-accent shadow-lg dark:shadow-stone-900 xl:h-[400px]" > -
+
{/* Piano Keys Container */}
= ({
- +
);