1389
Assets/web/package-lock.json
generated
1389
Assets/web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -8,6 +8,7 @@
|
|||||||
"@fontsource/roboto": "^5.0.3",
|
"@fontsource/roboto": "^5.0.3",
|
||||||
"@mui/icons-material": "^5.13.7",
|
"@mui/icons-material": "^5.13.7",
|
||||||
"@mui/material": "^5.13.7",
|
"@mui/material": "^5.13.7",
|
||||||
|
"@tailwindcss/postcss": "^4.1.17",
|
||||||
"@testing-library/jest-dom": "^5.16.5",
|
"@testing-library/jest-dom": "^5.16.5",
|
||||||
"@testing-library/react": "^13.4.0",
|
"@testing-library/react": "^13.4.0",
|
||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
@ -44,9 +45,12 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"autoprefixer": "^10.4.21",
|
||||||
"eslint": "^8.43.0",
|
"eslint": "^8.43.0",
|
||||||
"eslint-plugin-react": "^7.32.2",
|
"eslint-plugin-react": "^7.32.2",
|
||||||
"npm-build-zip": "^1.0.4",
|
"npm-build-zip": "^1.0.4",
|
||||||
"prettier": "^3.6.2"
|
"postcss": "^8.5.6",
|
||||||
|
"prettier": "^3.6.2",
|
||||||
|
"tailwindcss": "^3.4.18"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -47,19 +47,56 @@ function App() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="min-h-screen bg-[#0f0f0f] p-4 relative overflow-hidden">
|
||||||
<Container>
|
{/* Controls Grid */}
|
||||||
<JuceSlider identifier="harmonyMix" title="Mix" />
|
<div className="grid grid-cols-3 gap-2 items-start">
|
||||||
<JuceSlider identifier="formantPreserve" title="Formant" />
|
{/* Left Column */}
|
||||||
<JuceCheckbox identifier="autoTuneEnabled" />
|
<div className="border border-[#2a2a2a] rounded-lg p-4">
|
||||||
<JuceSlider identifier="autoTuneSpeed" title="Auto Tune Speed" />
|
<div className="grid grid-cols-2 grid-rows-2 gap-4 items-end">
|
||||||
<JuceSlider identifier="autoTuneDepth" title="Auto Tune Depth" />
|
<div>
|
||||||
<JuceSlider identifier="portTime" title="Portamento Speed" />
|
<JuceSlider identifier="harmonyMix" title="Mix" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<JuceSlider identifier="formantPreserve" title="Formant" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<JuceSlider identifier="portTime" title="Portamento Speed" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<JuceSlider identifier="panWidth" title="Pan Width" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<JuceCheckbox identifier="freezeEnabled" />
|
<div className="border border-[#2a2a2a] rounded-lg p-4">
|
||||||
<JuceSlider identifier="freezePitch" title="Freeze Pitch" />
|
<div className="grid grid-cols-2 grid-rows-2 gap-4 items-end ">
|
||||||
<JuceSlider identifier="freezeVolume" title="Freeze Volume" />
|
<div className="col-span-2 self-center flex justify-center">
|
||||||
</Container>
|
<JuceCheckbox identifier="autoTuneEnabled" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<JuceSlider identifier="autoTuneSpeed" title="Auto Tune Speed" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<JuceSlider identifier="autoTuneDepth" title="Auto Tune Depth" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<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">
|
||||||
|
<JuceCheckbox identifier="freezeEnabled" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<JuceSlider identifier="freezePitch" title="Freeze Pitch" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<JuceSlider identifier="freezeVolume" title="Freeze Volume" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Container></Container>
|
||||||
<MidiNoteInfo />
|
<MidiNoteInfo />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
88
Assets/web/src/Components/HorizontalSlider.js
Normal file
88
Assets/web/src/Components/HorizontalSlider.js
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
/* eslint-disable react/prop-types */
|
||||||
|
import React, { useState, useRef, useEffect } from "react";
|
||||||
|
|
||||||
|
export function HorizontalSlider({
|
||||||
|
value = 50,
|
||||||
|
onChange,
|
||||||
|
min = 0,
|
||||||
|
max = 100,
|
||||||
|
showFill = false,
|
||||||
|
}) {
|
||||||
|
// const [localValue, setLocalValue] = useState(value);
|
||||||
|
const [percentage, setPercentage] = useState(
|
||||||
|
((value - min) / (max - min)) * 100
|
||||||
|
);
|
||||||
|
const sliderRef = useRef(null);
|
||||||
|
const isDragging = useRef(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setPercentage(((value - min) / (max - min)) * 100);
|
||||||
|
}, [value, min, max]);
|
||||||
|
|
||||||
|
const handleMouseDown = (e) => {
|
||||||
|
isDragging.current = true;
|
||||||
|
updateValue(e.clientX);
|
||||||
|
e.preventDefault();
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateValue = (clientX) => {
|
||||||
|
if (!sliderRef.current) return;
|
||||||
|
|
||||||
|
const rect = sliderRef.current.getBoundingClientRect();
|
||||||
|
const percentage = Math.max(
|
||||||
|
0,
|
||||||
|
Math.min(100, ((clientX - rect.left) / rect.width) * 100)
|
||||||
|
);
|
||||||
|
const newValue = min + (percentage / 100) * (max - min);
|
||||||
|
|
||||||
|
// setLocalValue(newValue);
|
||||||
|
onChange?.(newValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleMouseMove = (e) => {
|
||||||
|
if (!isDragging.current) return;
|
||||||
|
updateValue(e.clientX);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseUp = () => {
|
||||||
|
isDragging.current = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("mousemove", handleMouseMove);
|
||||||
|
document.addEventListener("mouseup", handleMouseUp);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("mousemove", handleMouseMove);
|
||||||
|
document.removeEventListener("mouseup", handleMouseUp);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative w-64 h-10">
|
||||||
|
<div
|
||||||
|
ref={sliderRef}
|
||||||
|
className="absolute top-1/2 -translate-y-1/2 w-full h-8 bg-[#1a1a1a] border-2 border-[#2a2a2a] rounded-full cursor-pointer select-none shadow-[inset_0_2px_8px_rgba(0,0,0,0.5),inset_0_-1px_3px_rgba(255,255,255,0.03)] overflow-hidden"
|
||||||
|
onMouseDown={handleMouseDown}
|
||||||
|
>
|
||||||
|
{/* Diffused top highlight for track */}
|
||||||
|
<div className="absolute -top-1 left-0 right-0 h-3/4 bg-gradient-to-b from-white/[0.06] via-white/[0.015] to-transparent rounded-full pointer-events-none blur-[2px]" />
|
||||||
|
|
||||||
|
{showFill && (
|
||||||
|
<div
|
||||||
|
className="absolute left-0 top-0 h-full bg-[#7FFF7F] rounded-full shadow-[0_0_8px_rgba(127,255,127,0.4)]"
|
||||||
|
style={{ width: `${percentage}%` }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Thumb outside the track container */}
|
||||||
|
<div
|
||||||
|
className="absolute top-1/2 -translate-y-1/2 w-10 h-10 bg-[#2a2a2a] border-2 border-[#3a3a3a] rounded-full shadow-[0_4px_8px_rgba(0,0,0,0.4),inset_0_-1px_4px_rgba(255,255,255,0.05),inset_0_2px_4px_rgba(0,0,0,0.3)] overflow-hidden pointer-events-none"
|
||||||
|
style={{ left: `calc(${percentage}% - 20px)` }}
|
||||||
|
>
|
||||||
|
<div className="absolute -top-1 left-0 right-0 h-3/4 bg-gradient-to-b from-white/[0.08] via-white/[0.02] to-transparent rounded-full pointer-events-none blur-[2px]" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -2,10 +2,11 @@ import React, { useState, useEffect } from "react";
|
|||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
import * as Juce from "juce-framework-frontend";
|
import * as Juce from "juce-framework-frontend";
|
||||||
import Checkbox from "@mui/material/Checkbox";
|
// import Checkbox from "@mui/material/Checkbox";
|
||||||
import FormGroup from "@mui/material/FormGroup";
|
// import FormGroup from "@mui/material/FormGroup";
|
||||||
import FormControlLabel from "@mui/material/FormControlLabel";
|
// import FormControlLabel from "@mui/material/FormControlLabel";
|
||||||
import { controlParameterIndexAnnotation } from "../types/JuceTypes.js";
|
import { controlParameterIndexAnnotation } from "../types/JuceTypes.js";
|
||||||
|
import { ToggleSwitch } from "./ToggleSwitch.js";
|
||||||
|
|
||||||
export default function JuceCheckbox({ identifier }) {
|
export default function JuceCheckbox({ identifier }) {
|
||||||
JuceCheckbox.propTypes = {
|
JuceCheckbox.propTypes = {
|
||||||
@ -15,11 +16,16 @@ export default function JuceCheckbox({ identifier }) {
|
|||||||
const checkboxState = Juce.getToggleState(identifier);
|
const checkboxState = Juce.getToggleState(identifier);
|
||||||
|
|
||||||
const [value, setValue] = useState(checkboxState.getValue());
|
const [value, setValue] = useState(checkboxState.getValue());
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
const [properties, setProperties] = useState(checkboxState.properties);
|
const [properties, setProperties] = useState(checkboxState.properties);
|
||||||
|
|
||||||
const handleChange = (event) => {
|
// const handleChange = (event) => {
|
||||||
checkboxState.setValue(event.target.checked);
|
// checkboxState.setValue(event.target.checked);
|
||||||
setValue(event.target.checked);
|
// setValue(event.target.checked);
|
||||||
|
// };
|
||||||
|
const handleChange = (value) => {
|
||||||
|
checkboxState.setValue(value);
|
||||||
|
setValue(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -37,18 +43,21 @@ export default function JuceCheckbox({ identifier }) {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const cb = <Checkbox checked={value} onChange={handleChange} />;
|
// const cb = <Checkbox checked={value} onChange={handleChange} />;
|
||||||
|
// const cb = <ToggleSwitch value={value} onChange={handleChange} />;
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
|
margin={0}
|
||||||
|
padding={0}
|
||||||
{...{
|
{...{
|
||||||
[controlParameterIndexAnnotation]:
|
[controlParameterIndexAnnotation]:
|
||||||
checkboxState.properties.parameterIndex,
|
checkboxState.properties.parameterIndex,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FormGroup>
|
<ToggleSwitch value={value} onChange={handleChange} />
|
||||||
<FormControlLabel control={cb} label={properties.name} />
|
{/* <FormGroup>
|
||||||
</FormGroup>
|
<FormControlLabel control={cb} />
|
||||||
|
</FormGroup> */}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,15 +2,16 @@ import PropTypes from "prop-types";
|
|||||||
import Box from "@mui/material/Container";
|
import Box from "@mui/material/Container";
|
||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
|
|
||||||
import Slider from "@mui/material/Slider";
|
// import Slider from "@mui/material/Slider";
|
||||||
import * as Juce from "juce-framework-frontend";
|
import * as Juce from "juce-framework-frontend";
|
||||||
import { React, useState, useEffect } from "react";
|
import { React, useState, useEffect } from "react";
|
||||||
import { controlParameterIndexAnnotation } from "../types/JuceTypes.js";
|
import { controlParameterIndexAnnotation } from "../types/JuceTypes.js";
|
||||||
|
import { Knob } from "./knob.js";
|
||||||
|
// import { HorizontalSlider } from "./HorizontalSlider.js";
|
||||||
|
|
||||||
export default function JuceSlider({ identifier, title }) {
|
export default function JuceSlider({ identifier }) {
|
||||||
JuceSlider.propTypes = {
|
JuceSlider.propTypes = {
|
||||||
identifier: PropTypes.string,
|
identifier: PropTypes.string,
|
||||||
title: PropTypes.string,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const sliderState = Juce.getSliderState(identifier);
|
const sliderState = Juce.getSliderState(identifier);
|
||||||
@ -18,19 +19,19 @@ export default function JuceSlider({ identifier, title }) {
|
|||||||
const [value, setValue] = useState(sliderState.getNormalisedValue());
|
const [value, setValue] = useState(sliderState.getNormalisedValue());
|
||||||
const [properties, setProperties] = useState(sliderState.properties);
|
const [properties, setProperties] = useState(sliderState.properties);
|
||||||
|
|
||||||
const handleChange = (event, newValue) => {
|
const handleChange = (newValue) => {
|
||||||
sliderState.setNormalisedValue(newValue);
|
sliderState.setNormalisedValue(newValue);
|
||||||
setValue(newValue);
|
setValue(newValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
const mouseDown = () => {
|
// const mouseDown = () => {
|
||||||
sliderState.sliderDragStarted();
|
// sliderState.sliderDragStarted();
|
||||||
};
|
// };
|
||||||
|
|
||||||
const changeCommitted = (event, newValue) => {
|
// const changeCommitted = (event, newValue) => {
|
||||||
sliderState.setNormalisedValue(newValue);
|
// sliderState.setNormalisedValue(newValue);
|
||||||
sliderState.sliderDragEnded();
|
// sliderState.sliderDragEnded();
|
||||||
};
|
// };
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const valueListenerId = sliderState.valueChangedEvent.addListener(() => {
|
const valueListenerId = sliderState.valueChangedEvent.addListener(() => {
|
||||||
@ -46,9 +47,9 @@ export default function JuceSlider({ identifier, title }) {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
function calculateValue() {
|
// function calculateValue() {
|
||||||
return sliderState.getScaledValue();
|
// return sliderState.getScaledValue();
|
||||||
}
|
// }
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@ -57,20 +58,36 @@ export default function JuceSlider({ identifier, title }) {
|
|||||||
sliderState.properties.parameterIndex,
|
sliderState.properties.parameterIndex,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography sx={{ mt: 1.5 }}>
|
<div className="justify-items-center">
|
||||||
{properties.name}: {sliderState.getScaledValue()} {properties.label}
|
<Typography
|
||||||
</Typography>
|
m={0}
|
||||||
<Slider
|
p={0}
|
||||||
aria-label={title}
|
fontSize={14}
|
||||||
value={value}
|
className="text-[#666666] text-center"
|
||||||
scale={calculateValue}
|
>
|
||||||
onChange={handleChange}
|
{properties.name}
|
||||||
min={0}
|
{properties.label}
|
||||||
max={1}
|
</Typography>
|
||||||
step={1 / (properties.numSteps - 1)}
|
<Knob value={value} onChange={handleChange} min={0} max={1} size="sm" />
|
||||||
onChangeCommitted={changeCommitted}
|
{/* <HorizontalSlider
|
||||||
onMouseDown={mouseDown}
|
value={value}
|
||||||
/>
|
onChange={handleChange}
|
||||||
|
min={0}
|
||||||
|
max={1}
|
||||||
|
showFill
|
||||||
|
/>
|
||||||
|
<Slider
|
||||||
|
aria-label={title}
|
||||||
|
value={value}
|
||||||
|
scale={calculateValue}
|
||||||
|
// onChange={handleChange}
|
||||||
|
min={0}
|
||||||
|
max={1}
|
||||||
|
step={1 / (properties.numSteps - 1)}
|
||||||
|
onChangeCommitted={changeCommitted}
|
||||||
|
onMouseDown={mouseDown}
|
||||||
|
/> */}
|
||||||
|
</div>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -57,7 +57,7 @@ export default function MidiNoteInfo() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ padding: "1rem" }}>
|
<div style={{ marginTop: "1rem" }}>
|
||||||
<PianoKeyboard heldNotes={notes} />
|
<PianoKeyboard heldNotes={notes} />
|
||||||
<h1>Autotune Note: {getCharfromNoteIndex(autotuneNote)}</h1>
|
<h1>Autotune Note: {getCharfromNoteIndex(autotuneNote)}</h1>
|
||||||
<label>Input cents</label>
|
<label>Input cents</label>
|
||||||
|
|||||||
@ -18,8 +18,8 @@ const NOTE_NAMES = [
|
|||||||
const WHITE_KEYS = [0, 2, 4, 5, 7, 9, 11];
|
const WHITE_KEYS = [0, 2, 4, 5, 7, 9, 11];
|
||||||
|
|
||||||
// C2 = 36, C5 = 72
|
// C2 = 36, C5 = 72
|
||||||
const LOWEST_MIDI = 36;
|
const LOWEST_MIDI = 24;
|
||||||
const HIGHEST_MIDI = 72;
|
const HIGHEST_MIDI = 84;
|
||||||
|
|
||||||
function getNoteName(midi) {
|
function getNoteName(midi) {
|
||||||
const octave = Math.floor(midi / 12) - 1;
|
const octave = Math.floor(midi / 12) - 1;
|
||||||
@ -72,7 +72,7 @@ export default function PianoKeyboard({ heldNotes }) {
|
|||||||
const idx = midiToWhiteIndex[prevWhite];
|
const idx = midiToWhiteIndex[prevWhite];
|
||||||
if (idx === undefined) return 0;
|
if (idx === undefined) return 0;
|
||||||
// Offset: (idx + 0.65) / numWhite * 100%
|
// Offset: (idx + 0.65) / numWhite * 100%
|
||||||
return ((idx + 0.65) / numWhite) * 100;
|
return ((idx + 1) / numWhite) * 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -102,8 +102,8 @@ export default function PianoKeyboard({ heldNotes }) {
|
|||||||
style={{
|
style={{
|
||||||
flex: "1 1 0",
|
flex: "1 1 0",
|
||||||
height: "100%",
|
height: "100%",
|
||||||
border: "1px solid #888",
|
border: "1px solid #2a2a2a",
|
||||||
background: k.held ? "#2a82caff" : "#fff",
|
background: k.held ? "#7fff7f" : "#222",
|
||||||
position: "relative",
|
position: "relative",
|
||||||
boxSizing: "border-box",
|
boxSizing: "border-box",
|
||||||
marginRight: -1,
|
marginRight: -1,
|
||||||
@ -123,11 +123,11 @@ export default function PianoKeyboard({ heldNotes }) {
|
|||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span style={{ opacity: 0.5 }}>{k.noteName}</span>
|
<span style={{ color: "#555" }}>{k.noteName}</span>
|
||||||
{k.held && (
|
{k.held && (
|
||||||
<span
|
<span
|
||||||
style={{
|
style={{
|
||||||
color: "#fff",
|
color: "#666666",
|
||||||
fontWeight: "bold",
|
fontWeight: "bold",
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
lineHeight: "14px",
|
lineHeight: "14px",
|
||||||
@ -161,8 +161,8 @@ export default function PianoKeyboard({ heldNotes }) {
|
|||||||
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 ? "#2a82caff" : "#222",
|
background: k.held ? "#7fff7f" : "#111",
|
||||||
border: "1px solid #444",
|
border: "1px solid #333",
|
||||||
borderRadius: 3,
|
borderRadius: 3,
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column-reverse",
|
flexDirection: "column-reverse",
|
||||||
@ -184,7 +184,7 @@ export default function PianoKeyboard({ heldNotes }) {
|
|||||||
{k.held && (
|
{k.held && (
|
||||||
<span
|
<span
|
||||||
style={{
|
style={{
|
||||||
color: "#fff",
|
color: "#666666",
|
||||||
fontWeight: "bold",
|
fontWeight: "bold",
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
lineHeight: "14px",
|
lineHeight: "14px",
|
||||||
|
|||||||
42
Assets/web/src/Components/ToggleSwitch.js
Normal file
42
Assets/web/src/Components/ToggleSwitch.js
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
/* eslint-disable react/prop-types */
|
||||||
|
import React, { useState } from "react";
|
||||||
|
|
||||||
|
export function ToggleSwitch({ value = true, onChange }) {
|
||||||
|
const [isOn, setIsOn] = useState(value);
|
||||||
|
|
||||||
|
const handleToggle = (newValue) => {
|
||||||
|
setIsOn(newValue);
|
||||||
|
onChange?.(newValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<button
|
||||||
|
onClick={() => handleToggle(true)}
|
||||||
|
className={`px-8 py-3 rounded transition-all 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)]"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{!isOn && (
|
||||||
|
<div className="absolute -top-1/4 left-0 right-0 h-3/4 bg-gradient-to-b from-white/[0.08] via-white/[0.02] to-transparent pointer-events-none blur-[2px]" />
|
||||||
|
)}
|
||||||
|
<span className="relative z-10">ON</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => handleToggle(false)}
|
||||||
|
className={`px-8 py-3 rounded transition-all 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)]"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{isOn && (
|
||||||
|
<div className="absolute -top-1/4 left-0 right-0 h-3/4 bg-gradient-to-b from-white/[0.08] via-white/[0.02] to-transparent pointer-events-none blur-[2px]" />
|
||||||
|
)}
|
||||||
|
<span className="relative z-10">OFF</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
95
Assets/web/src/Components/knob.js
Normal file
95
Assets/web/src/Components/knob.js
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
/* eslint-disable react/prop-types */
|
||||||
|
import React, { useState, useRef, useEffect } from "react";
|
||||||
|
|
||||||
|
// interface KnobProps {
|
||||||
|
// value?: number;
|
||||||
|
// onChange?: (value: number) => void;
|
||||||
|
// min?: number;
|
||||||
|
// max?: number;
|
||||||
|
// size?: 'sm' | 'md' | 'lg';
|
||||||
|
// }
|
||||||
|
|
||||||
|
export function Knob({ value = 0, onChange, min = 0, max = 100, size = "md" }) {
|
||||||
|
// const [localValue, setLocalValue] = useState(value);
|
||||||
|
const isDragging = useRef(false);
|
||||||
|
const startX = useRef(0);
|
||||||
|
const startY = useRef(0);
|
||||||
|
const startValue = useRef(0);
|
||||||
|
|
||||||
|
const sizeClasses = {
|
||||||
|
sm: "w-16 h-16",
|
||||||
|
md: "w-24 h-24",
|
||||||
|
lg: "w-32 h-32",
|
||||||
|
};
|
||||||
|
|
||||||
|
const dotSize = {
|
||||||
|
sm: "w-2 h-2",
|
||||||
|
md: "w-2.5 h-2.5",
|
||||||
|
lg: "w-3 h-3",
|
||||||
|
};
|
||||||
|
|
||||||
|
const [rotation, setRotation] = useState(
|
||||||
|
((value - min) / (max - min)) * 270 - 135
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleMouseDown = (e) => {
|
||||||
|
isDragging.current = true;
|
||||||
|
startX.current = e.clientX;
|
||||||
|
startY.current = e.clientY;
|
||||||
|
startValue.current = value;
|
||||||
|
e.preventDefault();
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleMouseMove = (e) => {
|
||||||
|
if (!isDragging.current) return;
|
||||||
|
|
||||||
|
const deltaX = e.clientX - startX.current;
|
||||||
|
const deltaY = startY.current - e.clientY;
|
||||||
|
const combinedDelta = deltaX + deltaY;
|
||||||
|
const sensitivity = 0.01;
|
||||||
|
const newValue = Math.max(
|
||||||
|
min,
|
||||||
|
Math.min(max, startValue.current + combinedDelta * sensitivity)
|
||||||
|
);
|
||||||
|
|
||||||
|
setRotation(((newValue - min) / (max - min)) * 270 - 135);
|
||||||
|
onChange?.(newValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseUp = () => {
|
||||||
|
isDragging.current = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("mousemove", handleMouseMove);
|
||||||
|
document.addEventListener("mouseup", handleMouseUp);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("mousemove", handleMouseMove);
|
||||||
|
document.removeEventListener("mouseup", handleMouseUp);
|
||||||
|
};
|
||||||
|
}, [min, max, onChange]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setRotation(((value - min) / (max - min)) * 270 - 135);
|
||||||
|
}, [value, min, max]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`${sizeClasses[size]} rounded-full bg-[#1a1a1a] border-2 border-[#2a2a2a] relative cursor-pointer select-none shadow-[inset_0_2px_8px_rgba(0,0,0,0.5),inset_0_-2px_6px_rgba(255,255,255,0.03),0_4px_12px_rgba(0,0,0,0.4)] overflow-hidden`}
|
||||||
|
onMouseDown={handleMouseDown}
|
||||||
|
>
|
||||||
|
{/* Diffused top highlight */}
|
||||||
|
<div className="absolute -top-1/4 left-0 right-0 h-3/4 bg-gradient-to-b from-white/[0.08] via-white/[0.02] to-transparent rounded-full pointer-events-none blur-sm" />
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="absolute inset-0 flex items-start justify-center pt-2"
|
||||||
|
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)]`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,3 +1,7 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||||
@ -12,3 +16,200 @@ code {
|
|||||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||||
monospace;
|
monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@custom-variant dark (&:is(.dark *));
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--font-size: 16px;
|
||||||
|
--background: #ffffff;
|
||||||
|
--foreground: oklch(0.145 0 0);
|
||||||
|
--card: #ffffff;
|
||||||
|
--card-foreground: oklch(0.145 0 0);
|
||||||
|
--popover: oklch(1 0 0);
|
||||||
|
--popover-foreground: oklch(0.145 0 0);
|
||||||
|
--primary: #030213;
|
||||||
|
--primary-foreground: oklch(1 0 0);
|
||||||
|
--secondary: oklch(0.95 0.0058 264.53);
|
||||||
|
--secondary-foreground: #030213;
|
||||||
|
--muted: #ececf0;
|
||||||
|
--muted-foreground: #717182;
|
||||||
|
--accent: #e9ebef;
|
||||||
|
--accent-foreground: #030213;
|
||||||
|
--destructive: #d4183d;
|
||||||
|
--destructive-foreground: #ffffff;
|
||||||
|
--border: rgba(0, 0, 0, 0.1);
|
||||||
|
--input: transparent;
|
||||||
|
--input-background: #f3f3f5;
|
||||||
|
--switch-background: #cbced4;
|
||||||
|
--font-weight-medium: 500;
|
||||||
|
--font-weight-normal: 400;
|
||||||
|
--ring: oklch(0.708 0 0);
|
||||||
|
--chart-1: oklch(0.646 0.222 41.116);
|
||||||
|
--chart-2: oklch(0.6 0.118 184.704);
|
||||||
|
--chart-3: oklch(0.398 0.07 227.392);
|
||||||
|
--chart-4: oklch(0.828 0.189 84.429);
|
||||||
|
--chart-5: oklch(0.769 0.188 70.08);
|
||||||
|
--radius: 0.625rem;
|
||||||
|
--sidebar: oklch(0.985 0 0);
|
||||||
|
--sidebar-foreground: oklch(0.145 0 0);
|
||||||
|
--sidebar-primary: #030213;
|
||||||
|
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||||
|
--sidebar-accent: oklch(0.97 0 0);
|
||||||
|
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||||
|
--sidebar-border: oklch(0.922 0 0);
|
||||||
|
--sidebar-ring: oklch(0.708 0 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
--background: oklch(0.145 0 0);
|
||||||
|
--foreground: oklch(0.985 0 0);
|
||||||
|
--card: oklch(0.145 0 0);
|
||||||
|
--card-foreground: oklch(0.985 0 0);
|
||||||
|
--popover: oklch(0.145 0 0);
|
||||||
|
--popover-foreground: oklch(0.985 0 0);
|
||||||
|
--primary: oklch(0.985 0 0);
|
||||||
|
--primary-foreground: oklch(0.205 0 0);
|
||||||
|
--secondary: oklch(0.269 0 0);
|
||||||
|
--secondary-foreground: oklch(0.985 0 0);
|
||||||
|
--muted: oklch(0.269 0 0);
|
||||||
|
--muted-foreground: oklch(0.708 0 0);
|
||||||
|
--accent: oklch(0.269 0 0);
|
||||||
|
--accent-foreground: oklch(0.985 0 0);
|
||||||
|
--destructive: oklch(0.396 0.141 25.723);
|
||||||
|
--destructive-foreground: oklch(0.637 0.237 25.331);
|
||||||
|
--border: oklch(0.269 0 0);
|
||||||
|
--input: oklch(0.269 0 0);
|
||||||
|
--ring: oklch(0.439 0 0);
|
||||||
|
--font-weight-medium: 500;
|
||||||
|
--font-weight-normal: 400;
|
||||||
|
--chart-1: oklch(0.488 0.243 264.376);
|
||||||
|
--chart-2: oklch(0.696 0.17 162.48);
|
||||||
|
--chart-3: oklch(0.769 0.188 70.08);
|
||||||
|
--chart-4: oklch(0.627 0.265 303.9);
|
||||||
|
--chart-5: oklch(0.645 0.246 16.439);
|
||||||
|
--sidebar: oklch(0.205 0 0);
|
||||||
|
--sidebar-foreground: oklch(0.985 0 0);
|
||||||
|
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||||
|
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||||
|
--sidebar-accent: oklch(0.269 0 0);
|
||||||
|
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||||
|
--sidebar-border: oklch(0.269 0 0);
|
||||||
|
--sidebar-ring: oklch(0.439 0 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@theme inline {
|
||||||
|
--color-background: var(--background);
|
||||||
|
--color-foreground: var(--foreground);
|
||||||
|
--color-card: var(--card);
|
||||||
|
--color-card-foreground: var(--card-foreground);
|
||||||
|
--color-popover: var(--popover);
|
||||||
|
--color-popover-foreground: var(--popover-foreground);
|
||||||
|
--color-primary: var(--primary);
|
||||||
|
--color-primary-foreground: var(--primary-foreground);
|
||||||
|
--color-secondary: var(--secondary);
|
||||||
|
--color-secondary-foreground: var(--secondary-foreground);
|
||||||
|
--color-muted: var(--muted);
|
||||||
|
--color-muted-foreground: var(--muted-foreground);
|
||||||
|
--color-accent: var(--accent);
|
||||||
|
--color-accent-foreground: var(--accent-foreground);
|
||||||
|
--color-destructive: var(--destructive);
|
||||||
|
--color-destructive-foreground: var(--destructive-foreground);
|
||||||
|
--color-border: var(--border);
|
||||||
|
--color-input: var(--input);
|
||||||
|
--color-input-background: var(--input-background);
|
||||||
|
--color-switch-background: var(--switch-background);
|
||||||
|
--color-ring: var(--ring);
|
||||||
|
--color-chart-1: var(--chart-1);
|
||||||
|
--color-chart-2: var(--chart-2);
|
||||||
|
--color-chart-3: var(--chart-3);
|
||||||
|
--color-chart-4: var(--chart-4);
|
||||||
|
--color-chart-5: var(--chart-5);
|
||||||
|
--radius-sm: calc(var(--radius) - 4px);
|
||||||
|
--radius-md: calc(var(--radius) - 2px);
|
||||||
|
--radius-lg: var(--radius);
|
||||||
|
--radius-xl: calc(var(--radius) + 4px);
|
||||||
|
--color-sidebar: var(--sidebar);
|
||||||
|
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||||
|
--color-sidebar-primary: var(--sidebar-primary);
|
||||||
|
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||||
|
--color-sidebar-accent: var(--sidebar-accent);
|
||||||
|
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||||
|
--color-sidebar-border: var(--sidebar-border);
|
||||||
|
--color-sidebar-ring: var(--sidebar-ring);
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
/* * {
|
||||||
|
@apply border-border outline-ring/50;
|
||||||
|
} */
|
||||||
|
|
||||||
|
/* body {
|
||||||
|
@apply bg-background text-foreground;
|
||||||
|
} */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base typography. This is not applied to elements which have an ancestor with a Tailwind text class.
|
||||||
|
*/
|
||||||
|
@layer base {
|
||||||
|
:where(:not(:has([class*=' text-']), :not(:has([class^='text-'])))) {
|
||||||
|
h1 {
|
||||||
|
font-size: var(--text-2xl);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: var(--text-xl);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: var(--text-lg);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-size: var(--text-base);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: var(--text-base);
|
||||||
|
font-weight: var(--font-weight-normal);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-size: var(--text-base);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
font-size: var(--text-base);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
font-size: var(--text-base);
|
||||||
|
font-weight: var(--font-weight-normal);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
font-size: var(--font-size);
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer utilities {
|
||||||
|
.bg-gradient-radial {
|
||||||
|
background-image: radial-gradient(circle, var(--tw-gradient-stops));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
8
Assets/web/tailwind.config.js
Normal file
8
Assets/web/tailwind.config.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
module.exports = {
|
||||||
|
content: ["./src/*.{js,jsx,ts,tsx}", "./src/**/*.{js,jsx,ts,tsx}"],
|
||||||
|
theme: {
|
||||||
|
extend: {},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
};
|
||||||
@ -177,7 +177,7 @@ WebViewPluginAudioProcessorEditor::WebViewPluginAudioProcessorEditor(WebViewPlug
|
|||||||
webComponent->goToURL(localDevServerAddress);
|
webComponent->goToURL(localDevServerAddress);
|
||||||
//webComponent.goToURL (WebBrowserComponent::getResourceProviderRoot());
|
//webComponent.goToURL (WebBrowserComponent::getResourceProviderRoot());
|
||||||
|
|
||||||
setSize(500, 500);
|
setSize(800, 500);
|
||||||
|
|
||||||
startTimerHz(60);
|
startTimerHz(60);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -71,6 +71,8 @@ void WebViewPluginAudioProcessor::processBlock(juce::AudioBuffer<float>& buffer,
|
|||||||
shifter.SetFreeze(state.getParameterAsValue("freezeEnabled").getValue());
|
shifter.SetFreeze(state.getParameterAsValue("freezeEnabled").getValue());
|
||||||
shifter.SetFreezePitchAdjust(state.getParameterAsValue("freezePitch").getValue());
|
shifter.SetFreezePitchAdjust(state.getParameterAsValue("freezePitch").getValue());
|
||||||
shifter.SetFreezeVolume(state.getParameterAsValue("freezeVolume").getValue());
|
shifter.SetFreezeVolume(state.getParameterAsValue("freezeVolume").getValue());
|
||||||
|
shifter.SetPanWidth(state.getParameterAsValue("panWidth").getValue());
|
||||||
|
|
||||||
|
|
||||||
juce::AudioBuffer<float> const_buff;
|
juce::AudioBuffer<float> const_buff;
|
||||||
const_buff.makeCopyOf(buffer);
|
const_buff.makeCopyOf(buffer);
|
||||||
|
|||||||
@ -100,6 +100,13 @@ public:
|
|||||||
"Freeze Volume",
|
"Freeze Volume",
|
||||||
NormalisableRange<float> {0.0f, 1.0f, .01f},
|
NormalisableRange<float> {0.0f, 1.0f, .01f},
|
||||||
0.5f);
|
0.5f);
|
||||||
|
|
||||||
|
sliderIds.push_back("panWidth");
|
||||||
|
addToLayout<AudioParameterFloat>(layout,
|
||||||
|
ParameterID("panWidth"),
|
||||||
|
"Pan Width",
|
||||||
|
NormalisableRange<float> {0.0f, 1.0f, .01f},
|
||||||
|
0.5f);
|
||||||
|
|
||||||
|
|
||||||
toggleIds.push_back("autoTuneEnabled");
|
toggleIds.push_back("autoTuneEnabled");
|
||||||
|
|||||||
@ -411,4 +411,11 @@ void Shifter::SetFreezePitchAdjust(float val) {
|
|||||||
for(int i = 0; i < MAX_VOICES; ++i) {
|
for(int i = 0; i < MAX_VOICES; ++i) {
|
||||||
freeze_voices[i].SetPitchAdjust(val);
|
freeze_voices[i].SetPitchAdjust(val);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Shifter::SetPanWidth(float val) {
|
||||||
|
for (int i = 0; i < MAX_VOICES; ++i) {
|
||||||
|
freeze_voices[i].SetPanWidth(val);
|
||||||
|
voices[i].SetPanWidth(val);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
341
Source/Shifter.h
341
Source/Shifter.h
@ -10,67 +10,67 @@
|
|||||||
template <typename T, size_t max_capacity>
|
template <typename T, size_t max_capacity>
|
||||||
class circ_queue {
|
class circ_queue {
|
||||||
public:
|
public:
|
||||||
circ_queue() {
|
circ_queue() {
|
||||||
head = buffer;
|
head = buffer;
|
||||||
tail = buffer;
|
tail = buffer;
|
||||||
size = 0;
|
size = 0;
|
||||||
capacity = max_capacity;
|
capacity = max_capacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
void push(T val) {
|
void push(T val) {
|
||||||
if (size == max_capacity) {
|
if (size == max_capacity) {
|
||||||
pop();
|
pop();
|
||||||
}
|
}
|
||||||
*tail = val;
|
*tail = val;
|
||||||
if (++tail >= buffer + max_capacity) {
|
if (++tail >= buffer + max_capacity) {
|
||||||
tail -= max_capacity;
|
tail -= max_capacity;
|
||||||
}
|
}
|
||||||
size++;
|
size++;
|
||||||
//*head = val;
|
//*head = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
void clear() {
|
void clear() {
|
||||||
head = buffer;
|
head = buffer;
|
||||||
tail = buffer;
|
tail = buffer;
|
||||||
size = 0;
|
size = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
T pop() {
|
T pop() {
|
||||||
if (size > 0) {
|
if (size > 0) {
|
||||||
T to_ret = *head;
|
T to_ret = *head;
|
||||||
if (++head >= buffer + max_capacity) {
|
if (++head >= buffer + max_capacity) {
|
||||||
head -= max_capacity;
|
head -= max_capacity;
|
||||||
}
|
}
|
||||||
--size;
|
--size;
|
||||||
return to_ret;
|
return to_ret;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return T();
|
return T();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
T& get(int indx) {
|
T& get(int indx) {
|
||||||
T* ret_ptr = head + indx;
|
T* ret_ptr = head + indx;
|
||||||
if (ret_ptr >= buffer + max_capacity) {
|
if (ret_ptr >= buffer + max_capacity) {
|
||||||
ret_ptr -= max_capacity;
|
ret_ptr -= max_capacity;
|
||||||
}
|
}
|
||||||
return *ret_ptr;
|
return *ret_ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
T& operator[](int indx) {
|
T& operator[](int indx) {
|
||||||
T* ret_ptr = head + indx;
|
T* ret_ptr = head + indx;
|
||||||
if (ret_ptr >= buffer + max_capacity) {
|
if (ret_ptr >= buffer + max_capacity) {
|
||||||
ret_ptr -= max_capacity;
|
ret_ptr -= max_capacity;
|
||||||
}
|
}
|
||||||
return *ret_ptr;
|
return *ret_ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t capacity;
|
size_t capacity;
|
||||||
// int size() { return capacity; }
|
// int size() { return capacity; }
|
||||||
size_t size;
|
size_t size;
|
||||||
T buffer[max_capacity];
|
T buffer[max_capacity];
|
||||||
T* head;
|
T* head;
|
||||||
T* tail;
|
T* tail;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -78,161 +78,162 @@ public:
|
|||||||
class MidiPitchSmoother {
|
class MidiPitchSmoother {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
MidiPitchSmoother() {
|
MidiPitchSmoother() {
|
||||||
tau = .1;
|
tau = .1;
|
||||||
dt = 2048.0f / 48000.0f;
|
dt = 2048.0f / 48000.0f;
|
||||||
PcorrPrev = 60.0f;
|
PcorrPrev = 60.0f;
|
||||||
PtargPrev = 60;
|
PtargPrev = 60;
|
||||||
depth = 1.0f;
|
depth = 1.0f;
|
||||||
inputPrev = 0;
|
inputPrev = 0;
|
||||||
}
|
}
|
||||||
void SetFrameTime(float frame_time) {
|
void SetFrameTime(float frame_time) {
|
||||||
dt = frame_time;
|
dt = frame_time;
|
||||||
}
|
}
|
||||||
void SetDepth(float d) {
|
void SetDepth(float d) {
|
||||||
// clamp to [0,1]
|
// clamp to [0,1]
|
||||||
if (d < 0.0f) d = 0.0f;
|
if (d < 0.0f) d = 0.0f;
|
||||||
if (d > 1.0f) d = 1.0f;
|
if (d > 1.0f) d = 1.0f;
|
||||||
depth = d;
|
depth = d;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetTimeConstant(float t) {
|
void SetTimeConstant(float t) {
|
||||||
tau = t;
|
tau = t;
|
||||||
}
|
}
|
||||||
float update(float Pdet, int Ptarget) {
|
float update(float Pdet, int Ptarget) {
|
||||||
// Detect large jump (new note)
|
// Detect large jump (new note)
|
||||||
float diff = Pdet - (int)Pdet;
|
float diff = Pdet - (int)Pdet;
|
||||||
|
|
||||||
if (Ptarget != PtargPrev) {
|
|
||||||
// Immediately reset to new note
|
|
||||||
PcorrPrev = diff;
|
|
||||||
PtargPrev = Ptarget;
|
|
||||||
inputPrev = Pdet;
|
|
||||||
return Ptarget+ PcorrPrev;
|
|
||||||
}
|
|
||||||
PtargPrev = Ptarget;
|
|
||||||
diff = Pdet - inputPrev;
|
|
||||||
|
|
||||||
// Compute smoothing coefficient
|
if (Ptarget != PtargPrev) {
|
||||||
float alpha = 1.0 - std::exp(-dt / tau);
|
// Immediately reset to new note
|
||||||
|
PcorrPrev = diff;
|
||||||
|
PtargPrev = Ptarget;
|
||||||
|
inputPrev = Pdet;
|
||||||
|
return Ptarget + PcorrPrev;
|
||||||
|
}
|
||||||
|
PtargPrev = Ptarget;
|
||||||
|
diff = Pdet - inputPrev;
|
||||||
|
|
||||||
// Compute smoothed pitch toward target
|
// Compute smoothing coefficient
|
||||||
float PcorrFull = PcorrPrev + alpha * (0 - PcorrPrev) * depth + (1 - depth) * diff;
|
float alpha = 1.0 - std::exp(-dt / tau);
|
||||||
|
|
||||||
// Apply depth: scale the correction amount
|
// Compute smoothed pitch toward target
|
||||||
inputPrev = Pdet;
|
float PcorrFull = PcorrPrev + alpha * (0 - PcorrPrev) * depth + (1 - depth) * diff;
|
||||||
PcorrPrev = PcorrFull;
|
|
||||||
return Ptarget + PcorrFull;
|
// Apply depth: scale the correction amount
|
||||||
}
|
inputPrev = Pdet;
|
||||||
|
PcorrPrev = PcorrFull;
|
||||||
|
return Ptarget + PcorrFull;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
float tau; // Time constant (s)
|
float tau; // Time constant (s)
|
||||||
float dt; // Frame time (s)
|
float dt; // Frame time (s)
|
||||||
float PcorrPrev; // Previous corrected pitch (MIDI)
|
float PcorrPrev; // Previous corrected pitch (MIDI)
|
||||||
int PtargPrev; // Previous corrected pitch (MIDI)
|
int PtargPrev; // Previous corrected pitch (MIDI)
|
||||||
float depth; // Amount of correction applied [0..1]
|
float depth; // Amount of correction applied [0..1]
|
||||||
float inputPrev;
|
float inputPrev;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Shifter {
|
class Shifter {
|
||||||
public:
|
public:
|
||||||
void Init(float samplerate, int samplesPerBlock);
|
void Init(float samplerate, int samplesPerBlock);
|
||||||
void Process(const float* const* in,
|
void Process(const float* const* in,
|
||||||
float** out,
|
float** out,
|
||||||
size_t size);
|
size_t size);
|
||||||
|
|
||||||
void AddMidiNote(int note);
|
void AddMidiNote(int note);
|
||||||
void RemoveMidiNote(int note);
|
void RemoveMidiNote(int note);
|
||||||
void SetFormantPreserve(float val) { formant_preserve = val; }
|
void SetFormantPreserve(float val) { formant_preserve = val; }
|
||||||
void SetAutoTuneSpeed(float val);
|
void SetAutoTuneSpeed(float val);
|
||||||
void SetAutoTuneDepth(float val);
|
void SetAutoTuneDepth(float val);
|
||||||
void SetPortamentoTime(float time) {
|
void SetPortamentoTime(float time) {
|
||||||
for (int i = 0; i < MAX_VOICES; ++i) {
|
for (int i = 0; i < MAX_VOICES; ++i) {
|
||||||
voices[i].SetPortamentoTime(time);
|
voices[i].SetPortamentoTime(time);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
float getInputPitch() const { return sample_rate_ / in_period; }
|
float getInputPitch() const { return sample_rate_ / in_period; }
|
||||||
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; }
|
||||||
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; }
|
||||||
|
void SetPanWidth(float val);
|
||||||
|
|
||||||
float out_midi = 40;
|
float out_midi = 40;
|
||||||
ShifterVoice voices[MAX_VOICES];
|
ShifterVoice voices[MAX_VOICES];
|
||||||
ShifterVoice freeze_voices[MAX_VOICES];
|
ShifterVoice freeze_voices[MAX_VOICES];
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void DetectPitch(const float* const* in, float** out, size_t size, size_t offset);
|
void DetectPitch(const float* const* in, float** out, size_t size, size_t offset);
|
||||||
void SetRates();
|
void SetRates();
|
||||||
void GetSamples(float** output, const float* input, size_t size, size_t offset);
|
void GetSamples(float** output, const float* input, size_t size, size_t offset);
|
||||||
float GetOutputEnvelopePeriod(int out_voice);
|
float GetOutputEnvelopePeriod(int out_voice);
|
||||||
float GetOutputEnvelopePeriodFreeze(int freeze_voice);
|
float GetOutputEnvelopePeriodFreeze(int freeze_voice);
|
||||||
int GetPeakIndex();
|
int GetPeakIndex();
|
||||||
void AddInterpolatedFrame(int voice, int max_index, float period_to_use);
|
void AddInterpolatedFrame(int voice, int max_index, float period_to_use);
|
||||||
void AddFreezeToOutput(int voice, float resampling_period);
|
void AddFreezeToOutput(int voice, float resampling_period);
|
||||||
void CopyInputToFreezeBuffer(int);
|
void CopyInputToFreezeBuffer(int);
|
||||||
|
|
||||||
Helmholtz helm;
|
Helmholtz helm;
|
||||||
// GranularSustain player;
|
// GranularSustain player;
|
||||||
|
|
||||||
int selected_sample;
|
int selected_sample;
|
||||||
bool playing;
|
bool playing;
|
||||||
bool looping;
|
bool looping;
|
||||||
bool loop_engaged;
|
bool loop_engaged;
|
||||||
float play_head;
|
float play_head;
|
||||||
float rate_factor;
|
float rate_factor;
|
||||||
float sample_freq = 440;
|
float sample_freq = 440;
|
||||||
float freq_target = 440;
|
float freq_target = 440;
|
||||||
float last_freq = 440;
|
float last_freq = 440;
|
||||||
double current_pitch = 440;
|
double current_pitch = 440;
|
||||||
double smear_thresh = .085;
|
double smear_thresh = .085;
|
||||||
double smear_step = 0.003;
|
double smear_step = 0.003;
|
||||||
bool onset_trigger = false;
|
bool onset_trigger = false;
|
||||||
bool pitch_trigger = false;
|
bool pitch_trigger = false;
|
||||||
float harmony_mix = 0.0f;
|
float harmony_mix = 0.0f;
|
||||||
float melody_mix = 0.0f;
|
float melody_mix = 0.0f;
|
||||||
bool freeze_needs_copy;
|
bool freeze_needs_copy;
|
||||||
|
|
||||||
int trigger_bank;
|
int trigger_bank;
|
||||||
|
|
||||||
circ_queue<float, 3> prev_freqs;
|
circ_queue<float, 3> prev_freqs;
|
||||||
|
|
||||||
float formant_preserve = 0;
|
float formant_preserve = 0;
|
||||||
|
|
||||||
|
|
||||||
float volume;
|
float volume;
|
||||||
float pitch_adj;
|
float pitch_adj;
|
||||||
bool last_record;
|
bool last_record;
|
||||||
int record_playhead;
|
int record_playhead;
|
||||||
bool dry_wet = false;
|
bool dry_wet = false;
|
||||||
bool is_pitched = true;
|
bool is_pitched = true;
|
||||||
float last_freqs[3];
|
float last_freqs[3];
|
||||||
float in_buffer[BUFFER_SIZE];
|
float in_buffer[BUFFER_SIZE];
|
||||||
float out_buffer[2][BUFFER_SIZE];
|
float out_buffer[2][BUFFER_SIZE];
|
||||||
float freeze_buffer[BUFFER_SIZE];
|
float freeze_buffer[BUFFER_SIZE];
|
||||||
int out_playhead = 0;
|
int out_playhead = 0;
|
||||||
int in_playhead = 0;
|
int in_playhead = 0;
|
||||||
int last_autotune_midi = -1;
|
int last_autotune_midi = -1;
|
||||||
|
|
||||||
float out_period_filter_amount = 0.7f; // You can expose this as a parameter
|
float out_period_filter_amount = 0.7f; // You can expose this as a parameter
|
||||||
|
|
||||||
bool freeze_mode = false;
|
bool freeze_mode = false;
|
||||||
|
|
||||||
float out_period = 0; //C3
|
float out_period = 0; //C3
|
||||||
float in_period = 366.936;
|
float in_period = 366.936;
|
||||||
float out_period_counter = 0;
|
float out_period_counter = 0;
|
||||||
float cos_lookup[8192];
|
float cos_lookup[8192];
|
||||||
float sample_rate_;
|
float sample_rate_;
|
||||||
int blocksize;
|
int blocksize;
|
||||||
bool enable_autotune = false;
|
bool enable_autotune = false;
|
||||||
float freeze_period;
|
float freeze_period;
|
||||||
float freeze_volume = 1;
|
float freeze_volume = 1;
|
||||||
MidiPitchSmoother out_midi_smoother;
|
MidiPitchSmoother out_midi_smoother;
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
@ -45,7 +45,24 @@ void ShifterVoice::Trigger(int midi_note) {
|
|||||||
// Retrigger envelope
|
// Retrigger envelope
|
||||||
amplitude_envelope_.Retrigger(false);
|
amplitude_envelope_.Retrigger(false);
|
||||||
onoff_ = true;
|
onoff_ = true;
|
||||||
|
|
||||||
|
float pan_min = 0;
|
||||||
|
float pan_max = 1;
|
||||||
|
if(pan_width < .5f)
|
||||||
|
pan_max = .1 + (pan_width * 2 * .9f);
|
||||||
|
else
|
||||||
|
pan_min = (pan_width - .5f) * 2 * .9f;
|
||||||
|
|
||||||
|
bool pan_left = rand() % 2;
|
||||||
|
|
||||||
panning = rand() / (float)RAND_MAX;
|
panning = rand() / (float)RAND_MAX;
|
||||||
|
panning = pan_min + (panning * (pan_max - pan_min));
|
||||||
|
if (pan_left) {
|
||||||
|
panning = .5 - panning * .5f;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
panning = .5 + panning * .5f;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShifterVoice::Release() {
|
void ShifterVoice::Release() {
|
||||||
|
|||||||
@ -33,6 +33,7 @@ public:
|
|||||||
float GetPanning(int channel) const;
|
float GetPanning(int channel) const;
|
||||||
int GetMidiNote() const { return current_midi; }
|
int GetMidiNote() const { return current_midi; }
|
||||||
void SetPitchAdjust(float);
|
void SetPitchAdjust(float);
|
||||||
|
void SetPanWidth(float val) { pan_width = val; }
|
||||||
bool onoff_;
|
bool onoff_;
|
||||||
|
|
||||||
float panning;
|
float panning;
|
||||||
@ -48,5 +49,5 @@ private:
|
|||||||
float current_amplitude;
|
float current_amplitude;
|
||||||
float period_counter;
|
float period_counter;
|
||||||
float pitch_adjust = 0.0f;
|
float pitch_adjust = 0.0f;
|
||||||
|
float pan_width = 0.0f;
|
||||||
};
|
};
|
||||||
Reference in New Issue
Block a user