Skip to content

Commit

Permalink
Minor Fixes + Rework Knob Movement Handling
Browse files Browse the repository at this point in the history
  • Loading branch information
gabe-serna committed Nov 9, 2024
1 parent 470f73a commit 4e98a68
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 29 deletions.
2 changes: 1 addition & 1 deletion app/audio/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export default function Page() {

return (
<div
className="flex w-full max-w-[1200px] flex-col items-center justify-around max-xl:px-20"
className="flex w-full max-w-[1200px] flex-col items-center justify-around px-8 sm:px-20 xl:px-0"
id="resize container"
>
{!audioStorage && <AudioForm />}
Expand Down
2 changes: 1 addition & 1 deletion app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export default function RootLayout({
{/* <nav className="flex h-16 w-full justify-center border-b border-b-foreground/10"></nav> */}
<div
id="main-content-container"
className="flex h-screen w-full flex-col items-center justify-center gap-20 p-5"
className="flex min-h-screen w-full flex-col items-center justify-center gap-20 p-5"
>
{children}
</div>
Expand Down
104 changes: 98 additions & 6 deletions components/PanKnob.tsx
Original file line number Diff line number Diff line change
@@ -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";

Expand All @@ -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<HTMLDivElement>(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<HTMLDivElement> = (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 (
<div className="mt-3 flex flex-col items-center">
<div
ref={knobRef}
onPointerDown={handlePointerDown}
className="mt-3 flex touch-none flex-col items-center"
>
<Knob
size={size}
angleOffset={-140}
Expand All @@ -29,7 +120,6 @@ const PanKnob = ({ value, setValue }: Props) => {
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);
}}
Expand Down Expand Up @@ -88,7 +178,9 @@ const PanKnob = ({ value, setValue }: Props) => {
L
</span>
<p
className={`mt-1 w-12 text-center text-card-foreground ${value < 0 ? "-translate-x-[0.1875rem]" : ""}`}
className={`mt-1 w-12 text-center text-card-foreground ${
value < 0 ? "-translate-x-[0.1875rem]" : ""
}`}
>
{value.toFixed(0)}
</p>
Expand Down
41 changes: 20 additions & 21 deletions components/PianoRoll.tsx
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -52,11 +54,10 @@ const PianoRoll: React.FC<PianoRollProps> = ({

// 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<string>, b: Set<string>): boolean => {
Expand Down Expand Up @@ -155,11 +156,12 @@ const PianoRoll: React.FC<PianoRollProps> = ({

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) => {
Expand Down Expand Up @@ -319,17 +321,11 @@ const PianoRoll: React.FC<PianoRollProps> = ({
ref={parentRef}
className="relative flex h-[300px] rounded-2xl bg-accent shadow-lg dark:shadow-stone-900 xl:h-[400px]"
>
<div
className="piano-roll-container flex w-full overflow-hidden rounded-2xl"
style={{ position: "relative" }}
>
<div className="piano-roll-container relative flex w-full overflow-hidden rounded-2xl">
{/* Piano Keys Container */}
<div
ref={pianoKeysContainerRef}
className="piano-keys-container no-scrollbar h-full overflow-hidden"
style={{
width: pianoKeyWidth,
}}
className="piano-keys-container no-scrollbar overflow-y-hidden"
>
<canvas
ref={pianoKeysCanvasRef}
Expand All @@ -354,9 +350,12 @@ const PianoRoll: React.FC<PianoRollProps> = ({
</div>

<div className="mt-[10px]">
<button onClick={isPlaying ? stopMidi : playMidi}>
{isPlaying ? "Stop" : "Play"}
</button>
<Button
onClick={isPlaying ? stopMidi : playMidi}
className="button-primary h-auto translate-y-4 rounded-xl bg-accent px-6 py-1 leading-none shadow-md *:size-6 *:stroke-yellow-500 dark:shadow-stone-900 *:dark:stroke-yellow-700"
>
{isPlaying ? <PauseCircle /> : <PlayCircle />}
</Button>
</div>
</>
);
Expand Down

0 comments on commit 4e98a68

Please sign in to comment.