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 */}
-
+
>
);