primary color and shadows
This commit is contained in:
@ -31,6 +31,7 @@ import * as Juce from "juce-framework-frontend";
|
||||
import JuceSlider from "./Components/JuceSlider.js";
|
||||
import JuceCheckbox from "./Components/JuceCheckbox.js";
|
||||
import MidiNoteInfo from "./Components/MidiNoteInfo.js";
|
||||
import AutoTuneInfo from "./Components/AutoTuneInfo.js";
|
||||
import { controlParameterIndexAnnotation } from "./types/JuceTypes.js";
|
||||
|
||||
import { React } from "react";
|
||||
@ -70,9 +71,16 @@ function App() {
|
||||
|
||||
<div className="border border-[#2a2a2a] rounded-lg p-4">
|
||||
<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">
|
||||
<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>
|
||||
<JuceSlider identifier="autoTuneSpeed" title="Auto Tune Speed" />
|
||||
</div>
|
||||
@ -84,7 +92,7 @@ function App() {
|
||||
|
||||
<div className="border border-[#2a2a2a] rounded-lg p-4">
|
||||
<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" />
|
||||
</div>
|
||||
<div>
|
||||
@ -97,8 +105,10 @@ function App() {
|
||||
</div>
|
||||
</div>
|
||||
<Container></Container>
|
||||
<div className="rounded-lg">
|
||||
<MidiNoteInfo />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
1
Assets/web/src/Colors.js
Normal file
1
Assets/web/src/Colors.js
Normal file
@ -0,0 +1 @@
|
||||
export const HIGHLIGHT_COLOR = "#5242cdff";
|
||||
76
Assets/web/src/Components/AutoTuneInfo.js
Normal file
76
Assets/web/src/Components/AutoTuneInfo.js
Normal 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>
|
||||
);
|
||||
}
|
||||
@ -1,12 +1,9 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import React from "react";
|
||||
|
||||
export default function CenterGrowSlider({
|
||||
value,
|
||||
min = -50,
|
||||
max = 50,
|
||||
positiveColor = "#4caf50",
|
||||
negativeColor = "#f44336",
|
||||
backgroundColor = "rgba(150,150,150,0.3)",
|
||||
height = 8,
|
||||
}) {
|
||||
@ -39,21 +36,21 @@ export default function CenterGrowSlider({
|
||||
>
|
||||
{/* Negative (left) bar */}
|
||||
<div
|
||||
className="bg-primary shadow-primary/40 shadow-[0_0_16px,inset_0_1px_2px_rgba(255,255,255,0.3)]"
|
||||
style={{
|
||||
...baseStyle,
|
||||
right: "50%", // anchored to the center
|
||||
width: `${negativeWidth}%`,
|
||||
backgroundColor: negativeColor,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Positive (right) bar */}
|
||||
<div
|
||||
className="bg-primary shadow-primary/40 shadow-[0_0_16px,inset_0_1px_2px_rgba(255,255,255,0.3)]"
|
||||
style={{
|
||||
...baseStyle,
|
||||
left: "50%", // anchored to the center
|
||||
width: `${positiveWidth}%`,
|
||||
backgroundColor: positiveColor,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import React, { useState, useRef, useEffect } from "react";
|
||||
import { HIGHLIGHT_COLOR } from "../Colors.js";
|
||||
|
||||
export function HorizontalSlider({
|
||||
value = 50,
|
||||
@ -70,7 +71,7 @@ export function HorizontalSlider({
|
||||
|
||||
{showFill && (
|
||||
<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}%` }}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import PianoKeyboard from "./PianoKeyboard";
|
||||
import MidNoteDataReceiver from "../DataRecievers/MidiNoteDataReceiver.js";
|
||||
import CenterGrowSlider from "./CenterGrowSlider.js";
|
||||
// import { Slider } from "@mui/material";
|
||||
// import { styled } from "@mui/material/styles";
|
||||
|
||||
@ -9,31 +8,9 @@ import CenterGrowSlider from "./CenterGrowSlider.js";
|
||||
|
||||
export default function MidiNoteInfo() {
|
||||
const [notes, setNotes] = useState([]);
|
||||
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) {
|
||||
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 MidNoteDataReceiver();
|
||||
isActiveRef.current = true;
|
||||
@ -42,9 +19,6 @@ export default function MidiNoteInfo() {
|
||||
if (!isActiveRef.current) return;
|
||||
if (dataReceiverRef.current) {
|
||||
setNotes(dataReceiverRef.current.getNotes());
|
||||
setInputCents(dataReceiverRef.current.getInputCents());
|
||||
setOutputCents(dataReceiverRef.current.getOutputCents());
|
||||
setAutotuneNote(dataReceiverRef.current.getAutotuneNote());
|
||||
}
|
||||
window.requestAnimationFrame(render);
|
||||
}
|
||||
@ -57,13 +31,8 @@ export default function MidiNoteInfo() {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div style={{ marginTop: "1rem" }}>
|
||||
<div className="rounded-lg" style={{ marginTop: "1rem" }}>
|
||||
<PianoKeyboard heldNotes={notes} />
|
||||
<h1>Autotune Note: {getCharfromNoteIndex(autotuneNote)}</h1>
|
||||
<label>Input cents</label>
|
||||
<CenterGrowSlider value={inputCents} />
|
||||
<label>Output cents</label>
|
||||
<CenterGrowSlider value={outputCents} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import React /*, { useRef, useEffect, useState }*/ from "react";
|
||||
|
||||
const NOTE_NAMES = [
|
||||
"C",
|
||||
"C♯",
|
||||
@ -96,14 +95,18 @@ export default function PianoKeyboard({ heldNotes }) {
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
{whiteKeys.map((k) => (
|
||||
{whiteKeys.map((k, i) => (
|
||||
<div
|
||||
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={{
|
||||
flex: "1 1 0",
|
||||
height: "100%",
|
||||
border: "1px solid #2a2a2a",
|
||||
background: k.held ? "#7fff7f" : "#222",
|
||||
position: "relative",
|
||||
boxSizing: "border-box",
|
||||
marginRight: -1,
|
||||
@ -114,6 +117,10 @@ export default function PianoKeyboard({ heldNotes }) {
|
||||
fontSize: 10,
|
||||
fontFamily: "monospace",
|
||||
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
|
||||
@ -156,12 +163,16 @@ export default function PianoKeyboard({ heldNotes }) {
|
||||
{blackKeys.map((k) => (
|
||||
<div
|
||||
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={{
|
||||
position: "absolute",
|
||||
left: `${getBlackKeyPercent(k.midi)}%`,
|
||||
width: `${(100 / numWhite) * 0.65}%`,
|
||||
height: "100%",
|
||||
background: k.held ? "#7fff7f" : "#111",
|
||||
border: "1px solid #333",
|
||||
borderRadius: 3,
|
||||
display: "flex",
|
||||
|
||||
@ -13,10 +13,10 @@ export function ToggleSwitch({ value = true, onChange }) {
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
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
|
||||
? "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-[#1a1a1a] text-[#7FFF7F] border-2 border-[#2a2a2a] shadow-[inset_0_2px_4px_rgba(0,0,0,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-primary border-2 border-[#2a2a2a] shadow-[inset_0_2px_4px_rgba(0,0,0,0.3)]`
|
||||
}`}
|
||||
>
|
||||
{!isOn && (
|
||||
@ -26,10 +26,10 @@ export function ToggleSwitch({ value = true, onChange }) {
|
||||
</button>
|
||||
<button
|
||||
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
|
||||
? "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-[#1a1a1a] text-[#7FFF7F] border-2 border-[#2a2a2a] shadow-[inset_0_2px_4px_rgba(0,0,0,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-primary border-2 border-[#2a2a2a] shadow-[inset_0_2px_4px_rgba(0,0,0,0.3)]`
|
||||
}`}
|
||||
>
|
||||
{isOn && (
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import React, { useState, useRef, useEffect } from "react";
|
||||
|
||||
// interface KnobProps {
|
||||
// value?: number;
|
||||
// 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)` }}
|
||||
>
|
||||
<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>
|
||||
|
||||
42
Assets/web/src/DataRecievers/AutoTuneDataReceiver.js
Normal file
42
Assets/web/src/DataRecievers/AutoTuneDataReceiver.js
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -12,17 +12,6 @@ export default class MidNoteDataReceiver {
|
||||
MidNoteDataReceiver_eventId,
|
||||
(event) => {
|
||||
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;
|
||||
}
|
||||
|
||||
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() {
|
||||
window.__JUCE__.backend.removeEventListener(this.midNoteDataRegistrationId);
|
||||
}
|
||||
|
||||
@ -95,6 +95,7 @@ code {
|
||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||
--sidebar-border: oklch(0.269 0 0);
|
||||
--sidebar-ring: oklch(0.439 0 0);
|
||||
--color-primary-rgb: 127,255,127;
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
|
||||
@ -2,7 +2,12 @@
|
||||
module.exports = {
|
||||
content: ["./src/*.{js,jsx,ts,tsx}", "./src/**/*.{js,jsx,ts,tsx}"],
|
||||
theme: {
|
||||
extend: {},
|
||||
extend: {
|
||||
colors: {
|
||||
//primary: "#7FFF7F",
|
||||
primary: "#2ccaffff",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
|
||||
@ -177,7 +177,7 @@ WebViewPluginAudioProcessorEditor::WebViewPluginAudioProcessorEditor(WebViewPlug
|
||||
webComponent->goToURL(localDevServerAddress);
|
||||
//webComponent.goToURL (WebBrowserComponent::getResourceProviderRoot());
|
||||
|
||||
setSize(800, 500);
|
||||
setSize(800, 390);
|
||||
|
||||
startTimerHz(60);
|
||||
}
|
||||
|
||||
@ -80,9 +80,19 @@ public:
|
||||
|
||||
DynamicObject::Ptr d(new DynamicObject());
|
||||
d->setProperty("notes", notes);
|
||||
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());
|
||||
webComponent->emitEventIfBrowserIsVisible("midNoteData", d.get());
|
||||
}
|
||||
else {
|
||||
d->setProperty("input_pitch", -1);
|
||||
d->setProperty("output_pitch", -1);
|
||||
}
|
||||
|
||||
webComponent->emitEventIfBrowserIsVisible("autoTuneData", d.get());
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@ -159,6 +159,7 @@ public:
|
||||
float getOutputPitch() const { return sample_rate_ / out_period; }
|
||||
void SetHarmonyMix(float mix);
|
||||
void SetAutoTuneEnable(bool enable) { enable_autotune = enable; }
|
||||
bool GetAutoTuneEnable() { return enable_autotune; }
|
||||
void SetFreeze(bool);
|
||||
void SetFreezePitchAdjust(float val);
|
||||
void SetFreezeVolume(float val) { freeze_volume = val; }
|
||||
|
||||
Reference in New Issue
Block a user