primary color and shadows

This commit is contained in:
michalcourson
2025-11-11 18:33:30 -05:00
parent b3429f03cb
commit 65f74cd725
16 changed files with 181 additions and 86 deletions

View File

@ -31,6 +31,7 @@ import * as Juce from "juce-framework-frontend";
import JuceSlider from "./Components/JuceSlider.js"; import JuceSlider from "./Components/JuceSlider.js";
import JuceCheckbox from "./Components/JuceCheckbox.js"; import JuceCheckbox from "./Components/JuceCheckbox.js";
import MidiNoteInfo from "./Components/MidiNoteInfo.js"; import MidiNoteInfo from "./Components/MidiNoteInfo.js";
import AutoTuneInfo from "./Components/AutoTuneInfo.js";
import { controlParameterIndexAnnotation } from "./types/JuceTypes.js"; import { controlParameterIndexAnnotation } from "./types/JuceTypes.js";
import { React } from "react"; import { React } from "react";
@ -70,8 +71,15 @@ function App() {
<div className="border border-[#2a2a2a] rounded-lg p-4"> <div className="border border-[#2a2a2a] rounded-lg p-4">
<div className="grid grid-cols-2 grid-rows-2 gap-4 items-end "> <div className="grid grid-cols-2 grid-rows-2 gap-4 items-end ">
<div className="col-span-2 self-center flex justify-center"> <div className="col-span-2">
<JuceCheckbox identifier="autoTuneEnabled" /> <div className="grid grid-rows-2 self-center flex justify-center items-start">
<div className="self-center justify-center items-center flex">
<JuceCheckbox identifier="autoTuneEnabled" />
</div>
<div className="mt-1">
<AutoTuneInfo />
</div>
</div>
</div> </div>
<div> <div>
<JuceSlider identifier="autoTuneSpeed" title="Auto Tune Speed" /> <JuceSlider identifier="autoTuneSpeed" title="Auto Tune Speed" />
@ -84,7 +92,7 @@ function App() {
<div className="border border-[#2a2a2a] rounded-lg p-4"> <div className="border border-[#2a2a2a] rounded-lg p-4">
<div className="grid grid-cols-2 grid-rows-2 gap-4 items-end"> <div className="grid grid-cols-2 grid-rows-2 gap-4 items-end">
<div className="mt-0 pt-0 col-span-2 self-center flex justify-center"> <div className="mt-0 pt-0 col-span-2 self-start flex justify-center ">
<JuceCheckbox identifier="freezeEnabled" /> <JuceCheckbox identifier="freezeEnabled" />
</div> </div>
<div> <div>
@ -97,7 +105,9 @@ function App() {
</div> </div>
</div> </div>
<Container></Container> <Container></Container>
<MidiNoteInfo /> <div className="rounded-lg">
<MidiNoteInfo />
</div>
</div> </div>
); );
} }

1
Assets/web/src/Colors.js Normal file
View File

@ -0,0 +1 @@
export const HIGHLIGHT_COLOR = "#5242cdff";

View File

@ -0,0 +1,76 @@
/* eslint-disable no-unused-vars */
import React, { useState, useEffect, useRef } from "react";
import CenterGrowSlider from "./CenterGrowSlider.js";
import AutoTuneDataReceiver from "../DataRecievers/AutoTuneDataReceiver.js";
// import { Slider } from "@mui/material";
// import { styled } from "@mui/material/styles";
// eslint-disable-next-line react/prop-types
export default function AutoTuneInfo() {
const [inputCents, setInputCents] = useState(0);
const [outputCents, setOutputCents] = useState(0);
const [autotuneNote, setAutotuneNote] = useState(0);
const dataReceiverRef = useRef(null);
const isActiveRef = useRef(true);
function getCharfromNoteIndex(index) {
if (index === -1) return "-";
const NOTE_NAMES = [
"C",
"C♯",
"D",
"D♯",
"E",
"F",
"F♯",
"G",
"G♯",
"A",
"A♯",
"B",
];
if (typeof index !== "number" || isNaN(index)) return "";
return NOTE_NAMES[index % NOTE_NAMES.length];
}
useEffect(() => {
dataReceiverRef.current = new AutoTuneDataReceiver();
isActiveRef.current = true;
function render() {
if (!isActiveRef.current) return;
if (dataReceiverRef.current) {
setInputCents(dataReceiverRef.current.getInputCents());
setOutputCents(dataReceiverRef.current.getOutputCents());
setAutotuneNote(dataReceiverRef.current.getAutotuneNote());
}
window.requestAnimationFrame(render);
}
window.requestAnimationFrame(render);
return function cleanup() {
isActiveRef.current = false;
if (dataReceiverRef.current) dataReceiverRef.current.unregister();
};
}, []);
return (
<div className="flex">
<h1
className="w-8 flex-initial py-2 px-1 text-xl"
style={{ color: "#666" }}
>
{getCharfromNoteIndex(autotuneNote)}
</h1>
<div className=" py-2 px-1 flex-1">
<div className="py-1">
<CenterGrowSlider value={inputCents} />
</div>
<div className="py-1">
<CenterGrowSlider value={outputCents} />
</div>
</div>
</div>
);
}

View File

@ -1,12 +1,9 @@
/* eslint-disable react/prop-types */ /* eslint-disable react/prop-types */
import React from "react"; import React from "react";
export default function CenterGrowSlider({ export default function CenterGrowSlider({
value, value,
min = -50, min = -50,
max = 50, max = 50,
positiveColor = "#4caf50",
negativeColor = "#f44336",
backgroundColor = "rgba(150,150,150,0.3)", backgroundColor = "rgba(150,150,150,0.3)",
height = 8, height = 8,
}) { }) {
@ -39,21 +36,21 @@ export default function CenterGrowSlider({
> >
{/* Negative (left) bar */} {/* Negative (left) bar */}
<div <div
className="bg-primary shadow-primary/40 shadow-[0_0_16px,inset_0_1px_2px_rgba(255,255,255,0.3)]"
style={{ style={{
...baseStyle, ...baseStyle,
right: "50%", // anchored to the center right: "50%", // anchored to the center
width: `${negativeWidth}%`, width: `${negativeWidth}%`,
backgroundColor: negativeColor,
}} }}
/> />
{/* Positive (right) bar */} {/* Positive (right) bar */}
<div <div
className="bg-primary shadow-primary/40 shadow-[0_0_16px,inset_0_1px_2px_rgba(255,255,255,0.3)]"
style={{ style={{
...baseStyle, ...baseStyle,
left: "50%", // anchored to the center left: "50%", // anchored to the center
width: `${positiveWidth}%`, width: `${positiveWidth}%`,
backgroundColor: positiveColor,
}} }}
/> />
</div> </div>

View File

@ -1,5 +1,6 @@
/* eslint-disable react/prop-types */ /* eslint-disable react/prop-types */
import React, { useState, useRef, useEffect } from "react"; import React, { useState, useRef, useEffect } from "react";
import { HIGHLIGHT_COLOR } from "../Colors.js";
export function HorizontalSlider({ export function HorizontalSlider({
value = 50, value = 50,
@ -70,7 +71,7 @@ export function HorizontalSlider({
{showFill && ( {showFill && (
<div <div
className="absolute left-0 top-0 h-full bg-[#7FFF7F] rounded-full shadow-[0_0_8px_rgba(127,255,127,0.4)]" className={`absolute left-0 top-0 h-full bg-primary rounded-full shadow-primary/40 shadow-[0_0_8px]`}
style={{ width: `${percentage}%` }} style={{ width: `${percentage}%` }}
/> />
)} )}

View File

@ -1,7 +1,6 @@
import React, { useState, useEffect, useRef } from "react"; import React, { useState, useEffect, useRef } from "react";
import PianoKeyboard from "./PianoKeyboard"; import PianoKeyboard from "./PianoKeyboard";
import MidNoteDataReceiver from "../DataRecievers/MidiNoteDataReceiver.js"; import MidNoteDataReceiver from "../DataRecievers/MidiNoteDataReceiver.js";
import CenterGrowSlider from "./CenterGrowSlider.js";
// import { Slider } from "@mui/material"; // import { Slider } from "@mui/material";
// import { styled } from "@mui/material/styles"; // import { styled } from "@mui/material/styles";
@ -9,31 +8,9 @@ import CenterGrowSlider from "./CenterGrowSlider.js";
export default function MidiNoteInfo() { export default function MidiNoteInfo() {
const [notes, setNotes] = useState([]); const [notes, setNotes] = useState([]);
const [inputCents, setInputCents] = useState(0);
const [outputCents, setOutputCents] = useState(0);
const [autotuneNote, setAutotuneNote] = useState(0);
const dataReceiverRef = useRef(null); const dataReceiverRef = useRef(null);
const isActiveRef = useRef(true); const isActiveRef = useRef(true);
function getCharfromNoteIndex(index) {
const NOTE_NAMES = [
"C",
"C♯",
"D",
"D♯",
"E",
"F",
"F♯",
"G",
"G♯",
"A",
"A♯",
"B",
];
if (typeof index !== "number" || isNaN(index)) return "";
return NOTE_NAMES[index % NOTE_NAMES.length];
}
useEffect(() => { useEffect(() => {
dataReceiverRef.current = new MidNoteDataReceiver(); dataReceiverRef.current = new MidNoteDataReceiver();
isActiveRef.current = true; isActiveRef.current = true;
@ -42,9 +19,6 @@ export default function MidiNoteInfo() {
if (!isActiveRef.current) return; if (!isActiveRef.current) return;
if (dataReceiverRef.current) { if (dataReceiverRef.current) {
setNotes(dataReceiverRef.current.getNotes()); setNotes(dataReceiverRef.current.getNotes());
setInputCents(dataReceiverRef.current.getInputCents());
setOutputCents(dataReceiverRef.current.getOutputCents());
setAutotuneNote(dataReceiverRef.current.getAutotuneNote());
} }
window.requestAnimationFrame(render); window.requestAnimationFrame(render);
} }
@ -57,13 +31,8 @@ export default function MidiNoteInfo() {
}, []); }, []);
return ( return (
<div style={{ marginTop: "1rem" }}> <div className="rounded-lg" style={{ marginTop: "1rem" }}>
<PianoKeyboard heldNotes={notes} /> <PianoKeyboard heldNotes={notes} />
<h1>Autotune Note: {getCharfromNoteIndex(autotuneNote)}</h1>
<label>Input cents</label>
<CenterGrowSlider value={inputCents} />
<label>Output cents</label>
<CenterGrowSlider value={outputCents} />
</div> </div>
); );
} }

View File

@ -1,6 +1,5 @@
/* eslint-disable react/prop-types */ /* eslint-disable react/prop-types */
import React /*, { useRef, useEffect, useState }*/ from "react"; import React /*, { useRef, useEffect, useState }*/ from "react";
const NOTE_NAMES = [ const NOTE_NAMES = [
"C", "C",
"C♯", "C♯",
@ -96,14 +95,18 @@ export default function PianoKeyboard({ heldNotes }) {
height: "100%", height: "100%",
}} }}
> >
{whiteKeys.map((k) => ( {whiteKeys.map((k, i) => (
<div <div
key={k.midi} key={k.midi}
className={
k.held
? "bg-primary shadow-primary/30 shadow-[0_0_16px,inset_0_1px_2px_rgba(255,255,255,0.2)]"
: "bg-[#222]"
}
style={{ style={{
flex: "1 1 0", flex: "1 1 0",
height: "100%", height: "100%",
border: "1px solid #2a2a2a", border: "1px solid #2a2a2a",
background: k.held ? "#7fff7f" : "#222",
position: "relative", position: "relative",
boxSizing: "border-box", boxSizing: "border-box",
marginRight: -1, marginRight: -1,
@ -114,6 +117,10 @@ export default function PianoKeyboard({ heldNotes }) {
fontSize: 10, fontSize: 10,
fontFamily: "monospace", fontFamily: "monospace",
overflow: "hidden", overflow: "hidden",
borderTopLeftRadius: i === 0 ? 6 : 0, // round left edge of first key
borderBottomLeftRadius: i === 0 ? 6 : 0,
borderTopRightRadius: i === whiteKeys.length - 1 ? 6 : 0, // round right edge of last key
borderBottomRightRadius: i === whiteKeys.length - 1 ? 6 : 0,
}} }}
> >
<div <div
@ -156,12 +163,16 @@ export default function PianoKeyboard({ heldNotes }) {
{blackKeys.map((k) => ( {blackKeys.map((k) => (
<div <div
key={k.midi} key={k.midi}
className={
k.held
? "bg-primary shadow-primary/30 shadow-[0_0_16px,inset_0_1px_2px_rgba(255,255,255,0.2)]"
: "bg-[#111]"
}
style={{ style={{
position: "absolute", position: "absolute",
left: `${getBlackKeyPercent(k.midi)}%`, left: `${getBlackKeyPercent(k.midi)}%`,
width: `${(100 / numWhite) * 0.65}%`, width: `${(100 / numWhite) * 0.65}%`,
height: "100%", height: "100%",
background: k.held ? "#7fff7f" : "#111",
border: "1px solid #333", border: "1px solid #333",
borderRadius: 3, borderRadius: 3,
display: "flex", display: "flex",

View File

@ -13,10 +13,10 @@ export function ToggleSwitch({ value = true, onChange }) {
<div className="flex gap-2"> <div className="flex gap-2">
<button <button
onClick={() => handleToggle(true)} onClick={() => handleToggle(true)}
className={`px-8 py-3 rounded transition-all relative overflow-hidden ${ className={`px-8 py-3 rounded transition-colors transition-shadow relative overflow-hidden ${
isOn isOn
? "bg-[#7FFF7F] text-[#1a1a1a] shadow-[0_0_16px_rgba(127,255,127,0.4),inset_0_1px_2px_rgba(255,255,255,0.3)]" ? `bg-primary text-[#1a1a1a] shadow-primary/40 shadow-[0_0_16px,inset_0_1px_2px_rgba(255,255,255,0.3)]`
: "bg-[#1a1a1a] text-[#7FFF7F] border-2 border-[#2a2a2a] shadow-[inset_0_2px_4px_rgba(0,0,0,0.3)]" : `bg-[#1a1a1a] text-primary border-2 border-[#2a2a2a] shadow-[inset_0_2px_4px_rgba(0,0,0,0.3)]`
}`} }`}
> >
{!isOn && ( {!isOn && (
@ -26,10 +26,10 @@ export function ToggleSwitch({ value = true, onChange }) {
</button> </button>
<button <button
onClick={() => handleToggle(false)} onClick={() => handleToggle(false)}
className={`px-8 py-3 rounded transition-all relative overflow-hidden ${ className={`px-8 py-3 rounded transition-colors transition-shadow relative overflow-hidden ${
!isOn !isOn
? "bg-[#7FFF7F] text-[#1a1a1a] shadow-[0_0_16px_rgba(127,255,127,0.4),inset_0_1px_2px_rgba(255,255,255,0.3)]" ? `bg-primary text-[#1a1a1a] shadow-primary/40 shadow-[0_0_16px,inset_0_1px_2px_rgba(255,255,255,0.3)]`
: "bg-[#1a1a1a] text-[#7FFF7F] border-2 border-[#2a2a2a] shadow-[inset_0_2px_4px_rgba(0,0,0,0.3)]" : `bg-[#1a1a1a] text-primary border-2 border-[#2a2a2a] shadow-[inset_0_2px_4px_rgba(0,0,0,0.3)]`
}`} }`}
> >
{isOn && ( {isOn && (

View File

@ -1,6 +1,5 @@
/* eslint-disable react/prop-types */ /* eslint-disable react/prop-types */
import React, { useState, useRef, useEffect } from "react"; import React, { useState, useRef, useEffect } from "react";
// interface KnobProps { // interface KnobProps {
// value?: number; // value?: number;
// onChange?: (value: number) => void; // onChange?: (value: number) => void;
@ -87,7 +86,7 @@ export function Knob({ value = 0, onChange, min = 0, max = 100, size = "md" }) {
style={{ transform: `rotate(${rotation}deg)` }} style={{ transform: `rotate(${rotation}deg)` }}
> >
<div <div
className={`${dotSize[size]} rounded-full bg-[#7FFF7F] shadow-[0_0_12px_rgba(127,255,127,0.9),0_0_4px_rgba(127,255,127,1)]`} className={`${dotSize[size]} rounded-full bg-primary shadow-primary/90 shadow-[0_0_12px]`}
/> />
</div> </div>
</div> </div>

View File

@ -0,0 +1,42 @@
// import * as Juce from "juce-framework-frontend";
// import reportWebVitals from "../reportWebVitals";
const AutoTuneDataReceiver_eventId = "autoTuneData";
export default class AutoTuneDataReceiver {
constructor() {
this.input_pitch = 0;
this.output_pitch = 0;
let self = this;
this.autoTuneDataRegistrationId = window.__JUCE__.backend.addEventListener(
AutoTuneDataReceiver_eventId,
(event) => {
self.input_pitch = event.input_pitch;
self.output_pitch = event.output_pitch;
}
);
}
frequencytoMidi(frequency) {
return 69 + 12 * Math.log2(frequency / 440);
}
getAutotuneNote() {
if (this.output_pitch <= 0) return -1;
const midi = this.frequencytoMidi(this.output_pitch);
return Math.round(midi % 12);
}
getInputCents() {
const midi = this.frequencytoMidi(this.input_pitch);
return Math.round((midi - Math.round(midi)) * 100);
}
getOutputCents() {
const midi = this.frequencytoMidi(this.output_pitch);
return Math.round((midi - Math.round(midi)) * 100);
}
unregister() {
window.__JUCE__.backend.removeEventListener(
this.autoTuneDataRegistrationId
);
}
}

View File

@ -12,17 +12,6 @@ export default class MidNoteDataReceiver {
MidNoteDataReceiver_eventId, MidNoteDataReceiver_eventId,
(event) => { (event) => {
self.notes = event.notes; self.notes = event.notes;
self.input_pitch = event.input_pitch;
self.output_pitch = event.output_pitch;
console.log("in: " + self.input_pitch);
console.log("out: " + self.output_pitch);
// reportWebVitals(console.log(event));
// fetch(Juce.getBackendResourceAddress("midNoteData.json"))
// .then((response) => response.text())
// .then((text) => {
// const data = JSON.parse(text);
// self.notes = data.notes;
// });
} }
); );
} }
@ -31,23 +20,6 @@ export default class MidNoteDataReceiver {
return this.notes; return this.notes;
} }
frequencytoMidi(frequency) {
return 69 + 12 * Math.log2(frequency / 440);
}
getAutotuneNote() {
const midi = this.frequencytoMidi(this.output_pitch);
return Math.round(midi % 12);
}
getInputCents() {
const midi = this.frequencytoMidi(this.input_pitch);
return Math.round((midi - Math.round(midi)) * 100);
}
getOutputCents() {
const midi = this.frequencytoMidi(this.output_pitch);
return Math.round((midi - Math.round(midi)) * 100);
}
unregister() { unregister() {
window.__JUCE__.backend.removeEventListener(this.midNoteDataRegistrationId); window.__JUCE__.backend.removeEventListener(this.midNoteDataRegistrationId);
} }

View File

@ -95,6 +95,7 @@ code {
--sidebar-accent-foreground: oklch(0.985 0 0); --sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(0.269 0 0); --sidebar-border: oklch(0.269 0 0);
--sidebar-ring: oklch(0.439 0 0); --sidebar-ring: oklch(0.439 0 0);
--color-primary-rgb: 127,255,127;
} }
@theme inline { @theme inline {

View File

@ -2,7 +2,12 @@
module.exports = { module.exports = {
content: ["./src/*.{js,jsx,ts,tsx}", "./src/**/*.{js,jsx,ts,tsx}"], content: ["./src/*.{js,jsx,ts,tsx}", "./src/**/*.{js,jsx,ts,tsx}"],
theme: { theme: {
extend: {}, extend: {
colors: {
//primary: "#7FFF7F",
primary: "#2ccaffff",
},
},
}, },
plugins: [], plugins: [],
}; };

View File

@ -177,7 +177,7 @@ WebViewPluginAudioProcessorEditor::WebViewPluginAudioProcessorEditor(WebViewPlug
webComponent->goToURL(localDevServerAddress); webComponent->goToURL(localDevServerAddress);
//webComponent.goToURL (WebBrowserComponent::getResourceProviderRoot()); //webComponent.goToURL (WebBrowserComponent::getResourceProviderRoot());
setSize(800, 500); setSize(800, 390);
startTimerHz(60); startTimerHz(60);
} }

View File

@ -80,9 +80,19 @@ public:
DynamicObject::Ptr d(new DynamicObject()); DynamicObject::Ptr d(new DynamicObject());
d->setProperty("notes", notes); d->setProperty("notes", notes);
d->setProperty("input_pitch", processorRef.shifter.getInputPitch());
d->setProperty("output_pitch", processorRef.shifter.getOutputPitch());
webComponent->emitEventIfBrowserIsVisible("midNoteData", d.get()); webComponent->emitEventIfBrowserIsVisible("midNoteData", d.get());
d->clear();
if (processorRef.shifter.GetAutoTuneEnable()) {
d->setProperty("input_pitch", processorRef.shifter.getInputPitch());
d->setProperty("output_pitch", processorRef.shifter.getOutputPitch());
}
else {
d->setProperty("input_pitch", -1);
d->setProperty("output_pitch", -1);
}
webComponent->emitEventIfBrowserIsVisible("autoTuneData", d.get());
} }
private: private:

View File

@ -159,6 +159,7 @@ public:
float getOutputPitch() const { return sample_rate_ / out_period; } float getOutputPitch() const { return sample_rate_ / out_period; }
void SetHarmonyMix(float mix); void SetHarmonyMix(float mix);
void SetAutoTuneEnable(bool enable) { enable_autotune = enable; } void SetAutoTuneEnable(bool enable) { enable_autotune = enable; }
bool GetAutoTuneEnable() { return enable_autotune; }
void SetFreeze(bool); void SetFreeze(bool);
void SetFreezePitchAdjust(float val); void SetFreezePitchAdjust(float val);
void SetFreezeVolume(float val) { freeze_volume = val; } void SetFreezeVolume(float val) { freeze_volume = val; }