autotune paramters, autotune in ui. UI organize
This commit is contained in:
19
Assets/web/package-lock.json
generated
19
Assets/web/package-lock.json
generated
@ -26,7 +26,8 @@
|
||||
"devDependencies": {
|
||||
"eslint": "^8.43.0",
|
||||
"eslint-plugin-react": "^7.32.2",
|
||||
"npm-build-zip": "^1.0.4"
|
||||
"npm-build-zip": "^1.0.4",
|
||||
"prettier": "^3.6.2"
|
||||
}
|
||||
},
|
||||
"../../../../../Downloads/JUCE/modules/juce_gui_extra/native/javascript": {
|
||||
@ -15391,6 +15392,22 @@
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
|
||||
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/pretty-bytes": {
|
||||
"version": "5.6.0",
|
||||
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz",
|
||||
|
||||
@ -46,6 +46,7 @@
|
||||
"devDependencies": {
|
||||
"eslint": "^8.43.0",
|
||||
"eslint-plugin-react": "^7.32.2",
|
||||
"npm-build-zip": "^1.0.4"
|
||||
"npm-build-zip": "^1.0.4",
|
||||
"prettier": "^3.6.2"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
@ -24,7 +24,7 @@
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>Ladder Filter</title>
|
||||
<title>Harmonizer</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"short_name": "Ladder Filter",
|
||||
"name": "Ladder Filter",
|
||||
"short_name": "Harmonizer",
|
||||
"name": "Harmonizer",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
|
||||
@ -26,419 +26,15 @@ import "@fontsource/roboto/400.css";
|
||||
import "@fontsource/roboto/500.css";
|
||||
import "@fontsource/roboto/700.css";
|
||||
|
||||
import Box from "@mui/material/Container";
|
||||
// import Checkbox from "@mui/material/Checkbox";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Container from "@mui/material/Container";
|
||||
import Slider from "@mui/material/Slider";
|
||||
// import Button from "@mui/material/Button";
|
||||
// import CardActions from "@mui/material/CardActions";
|
||||
// import Snackbar from "@mui/material/Snackbar";
|
||||
// import IconButton from "@mui/material/IconButton";
|
||||
// import CloseIcon from "@mui/icons-material/Close";
|
||||
// import InputLabel from "@mui/material/InputLabel";
|
||||
// import MenuItem from "@mui/material/MenuItem";
|
||||
// import FormControl from "@mui/material/FormControl";
|
||||
// import Select from "@mui/material/Select";
|
||||
// import FormGroup from "@mui/material/FormGroup";
|
||||
// import FormControlLabel from "@mui/material/FormControlLabel";
|
||||
import PianoKeyboard from "./Components/PianoKeyboard";
|
||||
|
||||
import { React, useState, useEffect, useRef } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import * as Juce from "juce-framework-frontend";
|
||||
import JuceSlider from "./Components/JuceSlider.js";
|
||||
import MidiNoteInfo from "./Components/MidiNoteInfo.js";
|
||||
import { controlParameterIndexAnnotation } from "./types/JuceTypes.js";
|
||||
|
||||
import { React } from "react";
|
||||
|
||||
import "./App.css";
|
||||
// import { KnobPercentage } from "./Components/KnobPercentage";
|
||||
|
||||
// Custom attributes in React must be in all lower case
|
||||
const controlParameterIndexAnnotation = "controlparameterindex";
|
||||
|
||||
function JuceSlider({ identifier, title }) {
|
||||
JuceSlider.propTypes = {
|
||||
identifier: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
};
|
||||
|
||||
const sliderState = Juce.getSliderState(identifier);
|
||||
|
||||
const [value, setValue] = useState(sliderState.getNormalisedValue());
|
||||
const [properties, setProperties] = useState(sliderState.properties);
|
||||
|
||||
const handleChange = (event, newValue) => {
|
||||
sliderState.setNormalisedValue(newValue);
|
||||
setValue(newValue);
|
||||
};
|
||||
|
||||
const mouseDown = () => {
|
||||
sliderState.sliderDragStarted();
|
||||
};
|
||||
|
||||
const changeCommitted = (event, newValue) => {
|
||||
sliderState.setNormalisedValue(newValue);
|
||||
sliderState.sliderDragEnded();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const valueListenerId = sliderState.valueChangedEvent.addListener(() => {
|
||||
setValue(sliderState.getNormalisedValue());
|
||||
});
|
||||
const propertiesListenerId = sliderState.propertiesChangedEvent.addListener(
|
||||
() => setProperties(sliderState.properties)
|
||||
);
|
||||
|
||||
return function cleanup() {
|
||||
sliderState.valueChangedEvent.removeListener(valueListenerId);
|
||||
sliderState.propertiesChangedEvent.removeListener(propertiesListenerId);
|
||||
};
|
||||
});
|
||||
|
||||
function calculateValue() {
|
||||
return sliderState.getScaledValue();
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
{...{
|
||||
[controlParameterIndexAnnotation]:
|
||||
sliderState.properties.parameterIndex,
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ mt: 1.5 }}>
|
||||
{properties.name}: {sliderState.getScaledValue()} {properties.label}
|
||||
</Typography>
|
||||
<Slider
|
||||
aria-label={title}
|
||||
value={value}
|
||||
scale={calculateValue}
|
||||
onChange={handleChange}
|
||||
min={0}
|
||||
max={1}
|
||||
step={1 / (properties.numSteps - 1)}
|
||||
onChangeCommitted={changeCommitted}
|
||||
onMouseDown={mouseDown}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// function JuceCheckbox({ identifier }) {
|
||||
// JuceCheckbox.propTypes = {
|
||||
// identifier: PropTypes.string,
|
||||
// };
|
||||
|
||||
// const checkboxState = Juce.getToggleState(identifier);
|
||||
|
||||
// const [value, setValue] = useState(checkboxState.getValue());
|
||||
// const [properties, setProperties] = useState(checkboxState.properties);
|
||||
|
||||
// const handleChange = (event) => {
|
||||
// checkboxState.setValue(event.target.checked);
|
||||
// setValue(event.target.checked);
|
||||
// };
|
||||
|
||||
// useEffect(() => {
|
||||
// const valueListenerId = checkboxState.valueChangedEvent.addListener(() => {
|
||||
// setValue(checkboxState.getValue());
|
||||
// });
|
||||
// const propertiesListenerId =
|
||||
// checkboxState.propertiesChangedEvent.addListener(() =>
|
||||
// setProperties(checkboxState.properties)
|
||||
// );
|
||||
|
||||
// return function cleanup() {
|
||||
// checkboxState.valueChangedEvent.removeListener(valueListenerId);
|
||||
// checkboxState.propertiesChangedEvent.removeListener(propertiesListenerId);
|
||||
// };
|
||||
// });
|
||||
|
||||
// const cb = <Checkbox checked={value} onChange={handleChange} />;
|
||||
|
||||
// return (
|
||||
// <Box
|
||||
// {...{
|
||||
// [controlParameterIndexAnnotation]:
|
||||
// checkboxState.properties.parameterIndex,
|
||||
// }}
|
||||
// >
|
||||
// <FormGroup>
|
||||
// <FormControlLabel control={cb} label={properties.name} />
|
||||
// </FormGroup>
|
||||
// </Box>
|
||||
// );
|
||||
// }
|
||||
|
||||
// function JuceComboBox({ identifier }) {
|
||||
// JuceComboBox.propTypes = {
|
||||
// identifier: PropTypes.string,
|
||||
// };
|
||||
|
||||
// const comboBoxState = Juce.getComboBoxState(identifier);
|
||||
|
||||
// const [value, setValue] = useState(comboBoxState.getChoiceIndex());
|
||||
// const [properties, setProperties] = useState(comboBoxState.properties);
|
||||
|
||||
// const handleChange = (event) => {
|
||||
// comboBoxState.setChoiceIndex(event.target.value);
|
||||
// setValue(event.target.value);
|
||||
// };
|
||||
|
||||
// useEffect(() => {
|
||||
// const valueListenerId = comboBoxState.valueChangedEvent.addListener(() => {
|
||||
// setValue(comboBoxState.getChoiceIndex());
|
||||
// });
|
||||
// const propertiesListenerId =
|
||||
// comboBoxState.propertiesChangedEvent.addListener(() => {
|
||||
// setProperties(comboBoxState.properties);
|
||||
// });
|
||||
|
||||
// return function cleanup() {
|
||||
// comboBoxState.valueChangedEvent.removeListener(valueListenerId);
|
||||
// comboBoxState.propertiesChangedEvent.removeListener(propertiesListenerId);
|
||||
// };
|
||||
// });
|
||||
|
||||
// return (
|
||||
// <Box
|
||||
// {...{
|
||||
// [controlParameterIndexAnnotation]:
|
||||
// comboBoxState.properties.parameterIndex,
|
||||
// }}
|
||||
// >
|
||||
// <FormControl fullWidth>
|
||||
// <InputLabel id={identifier}>{properties.name}</InputLabel>
|
||||
// <Select
|
||||
// labelId={identifier}
|
||||
// value={value}
|
||||
// label={properties.name}
|
||||
// onChange={handleChange}
|
||||
// >
|
||||
// {properties.choices.map((choice, i) => (
|
||||
// <MenuItem value={i} key={i}>
|
||||
// {choice}
|
||||
// </MenuItem>
|
||||
// ))}
|
||||
// </Select>
|
||||
// </FormControl>
|
||||
// </Box>
|
||||
// );
|
||||
// }
|
||||
|
||||
// const sayHello = Juce.getNativeFunction("sayHello");
|
||||
|
||||
// const SpectrumDataReceiver_eventId = "spectrumData";
|
||||
|
||||
// function interpolate(a, b, s) {
|
||||
// let result = new Array(a.length).fill(0);
|
||||
|
||||
// for (const [i, val] of a.entries()) result[i] += (1 - s) * val;
|
||||
|
||||
// for (const [i, val] of b.entries()) result[i] += s * val;
|
||||
|
||||
// return result;
|
||||
// }
|
||||
|
||||
// function mod(dividend, divisor) {
|
||||
// const quotient = Math.floor(dividend / divisor);
|
||||
// return dividend - divisor * quotient;
|
||||
// }
|
||||
|
||||
// class SpectrumDataReceiver {
|
||||
// constructor(bufferLength) {
|
||||
// this.bufferLength = bufferLength;
|
||||
// this.buffer = new Array(this.bufferLength);
|
||||
// this.readIndex = 0;
|
||||
// this.writeIndex = 0;
|
||||
// this.lastTimeStampMs = 0;
|
||||
// this.timeResolutionMs = 0;
|
||||
|
||||
// let self = this;
|
||||
// this.spectrumDataRegistrationId = window.__JUCE__.backend.addEventListener(
|
||||
// SpectrumDataReceiver_eventId,
|
||||
// () => {
|
||||
// fetch(Juce.getBackendResourceAddress("spectrumData.json"))
|
||||
// .then((response) => response.text())
|
||||
// .then((text) => {
|
||||
// const data = JSON.parse(text);
|
||||
|
||||
// if (self.timeResolutionMs == 0) {
|
||||
// self.timeResolutionMs = data.timeResolutionMs;
|
||||
|
||||
// // We want to stay behind the write index by a full batch plus one
|
||||
// // so that we can keep reading buffered frames until we receive the
|
||||
// // new batch
|
||||
// self.readIndex = -data.frames.length - 1;
|
||||
|
||||
// self.buffer.fill(new Array(data.frames[0].length).fill(0));
|
||||
// }
|
||||
|
||||
// for (const f of data.frames)
|
||||
// self.buffer[mod(self.writeIndex++, self.bufferLength)] = f;
|
||||
// });
|
||||
// }
|
||||
// );
|
||||
// }
|
||||
|
||||
// getBufferItem(index) {
|
||||
// return this.buffer[mod(index, this.buffer.length)];
|
||||
// }
|
||||
|
||||
// getLevels(timeStampMs) {
|
||||
// if (this.timeResolutionMs == 0) return null;
|
||||
|
||||
// const previousTimeStampMs = this.lastTimeStampMs;
|
||||
// this.lastTimeStampMs = timeStampMs;
|
||||
|
||||
// if (previousTimeStampMs == 0) return this.buffer[0];
|
||||
|
||||
// const timeAdvance =
|
||||
// (timeStampMs - previousTimeStampMs) / this.timeResolutionMs;
|
||||
// this.readIndex += timeAdvance;
|
||||
|
||||
// const integralPart = Math.floor(this.readIndex);
|
||||
// const fractionalPart = this.readIndex - integralPart;
|
||||
|
||||
// return interpolate(
|
||||
// this.getBufferItem(integralPart),
|
||||
// this.getBufferItem(integralPart + 1),
|
||||
// fractionalPart
|
||||
// );
|
||||
// }
|
||||
|
||||
// unregister() {
|
||||
// window.__JUCE__.backend.removeEventListener(
|
||||
// this.spectrumDataRegistrationId
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
const MidNoteDataReceiver_eventId = "midNoteData";
|
||||
class MidNoteDataReceiver {
|
||||
constructor() {
|
||||
this.notes = [];
|
||||
let self = this;
|
||||
this.midNoteDataRegistrationId = window.__JUCE__.backend.addEventListener(
|
||||
MidNoteDataReceiver_eventId,
|
||||
() => {
|
||||
fetch(Juce.getBackendResourceAddress("midNoteData.json"))
|
||||
.then((response) => response.text())
|
||||
.then((text) => {
|
||||
const data = JSON.parse(text);
|
||||
self.notes = data.notes;
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
getNotes() {
|
||||
return this.notes;
|
||||
}
|
||||
|
||||
unregister() {
|
||||
window.__JUCE__.backend.removeEventListener(this.midNoteDataRegistrationId);
|
||||
}
|
||||
}
|
||||
|
||||
function MidiNoteInfo() {
|
||||
const [notes, setNotes] = useState([]);
|
||||
const dataReceiverRef = useRef(null);
|
||||
const isActiveRef = useRef(true);
|
||||
|
||||
useEffect(() => {
|
||||
dataReceiverRef.current = new MidNoteDataReceiver();
|
||||
isActiveRef.current = true;
|
||||
|
||||
function render() {
|
||||
if (dataReceiverRef.current) {
|
||||
setNotes(dataReceiverRef.current.getNotes());
|
||||
}
|
||||
if (isActiveRef.current) {
|
||||
window.requestAnimationFrame(render);
|
||||
}
|
||||
}
|
||||
|
||||
window.requestAnimationFrame(render);
|
||||
|
||||
return function cleanup() {
|
||||
isActiveRef.current = false;
|
||||
if (dataReceiverRef.current) {
|
||||
dataReceiverRef.current.unregister();
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<PianoKeyboard heldNotes={notes} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// function FreqBandInfo() {
|
||||
// const canvasRef = useRef(null);
|
||||
// let dataReceiver = null;
|
||||
// let isActive = true;
|
||||
|
||||
// // eslint-disable-next-line no-unused-vars
|
||||
// const render = (timeStampMs) => {
|
||||
// const canvas = canvasRef.current;
|
||||
// const ctx = canvas.getContext("2d");
|
||||
// ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
// var grd = ctx.createLinearGradient(0, 0, 0, canvas.height);
|
||||
// grd.addColorStop(0, "#1976d2");
|
||||
// grd.addColorStop(1, "#dae9f8");
|
||||
// ctx.fillStyle = grd;
|
||||
|
||||
// if (dataReceiver != null) {
|
||||
// const levels = dataReceiver.getLevels(timeStampMs);
|
||||
|
||||
// if (levels != null) {
|
||||
// const numBars = levels.length;
|
||||
// const barWidth = canvas.width / numBars;
|
||||
// const barHeight = canvas.height;
|
||||
|
||||
// for (const [i, l] of levels.entries()) {
|
||||
// ctx.fillRect(
|
||||
// i * barWidth,
|
||||
// barHeight - l * barHeight,
|
||||
// barWidth,
|
||||
// l * barHeight
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (isActive) window.requestAnimationFrame(render);
|
||||
// };
|
||||
|
||||
// useEffect(() => {
|
||||
// dataReceiver = new SpectrumDataReceiver(10);
|
||||
// isActive = true;
|
||||
// window.requestAnimationFrame(render);
|
||||
|
||||
// return function cleanup() {
|
||||
// isActive = false;
|
||||
// dataReceiver.unregister();
|
||||
// };
|
||||
// });
|
||||
|
||||
// const canvasStyle = {
|
||||
// marginLeft: "0",
|
||||
// marginRight: "0",
|
||||
// marginTop: "1em",
|
||||
// display: "block",
|
||||
// width: "94%",
|
||||
// bottom: "0",
|
||||
// position: "absolute",
|
||||
// };
|
||||
|
||||
// return (
|
||||
// <Box>
|
||||
// <canvas height={90} style={canvasStyle} ref={canvasRef}></canvas>
|
||||
// </Box>
|
||||
// );
|
||||
// }
|
||||
|
||||
function App() {
|
||||
const controlParameterIndexUpdater = new Juce.ControlParameterIndexUpdater(
|
||||
@ -449,83 +45,15 @@ function App() {
|
||||
controlParameterIndexUpdater.handleMouseMove(event);
|
||||
});
|
||||
|
||||
// const [open, setOpen] = useState(false);
|
||||
// const [snackbarMessage, setMessage] = useState("No message received yet");
|
||||
|
||||
// const openSnackbar = () => {
|
||||
// setOpen(true);
|
||||
// };
|
||||
|
||||
// const handleClose = (event, reason) => {
|
||||
// if (reason === "clickaway") {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// setOpen(false);
|
||||
// };
|
||||
|
||||
// const action = (
|
||||
// <>
|
||||
// <IconButton
|
||||
// size="small"
|
||||
// aria-label="close"
|
||||
// color="inherit"
|
||||
// onClick={handleClose}
|
||||
// >
|
||||
// <CloseIcon fontSize="small" />
|
||||
// </IconButton>
|
||||
// </>
|
||||
// );
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Container>
|
||||
<JuceSlider identifier="formantSlider" title="Formant" />
|
||||
<JuceSlider identifier="autoTuneSpeedSlider" title="Auto Tune Speed" />
|
||||
<JuceSlider identifier="autoTuneDepthSlider" title="Auto Tune Depth" />
|
||||
<JuceSlider identifier="portTimeSlider" title="Portamento Speed" />
|
||||
</Container>
|
||||
<MidiNoteInfo />
|
||||
{/* <CardActions style={{ justifyContent: "center" }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{ marginTop: 2 }}
|
||||
onClick={() => {
|
||||
sayHello("JUCE").then((result) => {
|
||||
setMessage(result);
|
||||
openSnackbar();
|
||||
});
|
||||
}}
|
||||
>
|
||||
Call backend function
|
||||
</Button>
|
||||
</CardActions>
|
||||
<CardActions style={{ justifyContent: "center" }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{ marginTop: 2 }}
|
||||
onClick={() => {
|
||||
fetch(Juce.getBackendResourceAddress("data.txt"))
|
||||
.then((response) => response.text())
|
||||
.then((text) => {
|
||||
setMessage("Data fetched: " + text);
|
||||
openSnackbar();
|
||||
});
|
||||
}}
|
||||
>
|
||||
Fetch data from backend
|
||||
</Button>
|
||||
</CardActions>
|
||||
<JuceCheckbox identifier="muteToggle" />
|
||||
<br></br>
|
||||
<JuceComboBox identifier="filterTypeCombo" /> */}
|
||||
|
||||
{/* <Snackbar
|
||||
open={open}
|
||||
autoHideDuration={6000}
|
||||
onClose={handleClose}
|
||||
message={snackbarMessage}
|
||||
action={action}
|
||||
/> */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
61
Assets/web/src/Components/CenterGrowSlider.js
Normal file
61
Assets/web/src/Components/CenterGrowSlider.js
Normal file
@ -0,0 +1,61 @@
|
||||
/* 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,
|
||||
}) {
|
||||
// Clamp the value
|
||||
const clamped = Math.max(min, Math.min(max, value));
|
||||
const range = max - min;
|
||||
const halfRange = range / 2;
|
||||
const magnitude = Math.abs(clamped) / halfRange; // 0..1
|
||||
|
||||
// Calculate widths (each bar maxes out at 50% width)
|
||||
const positiveWidth = clamped > 0 ? magnitude * 50 : 0;
|
||||
const negativeWidth = clamped < 0 ? magnitude * 50 : 0;
|
||||
|
||||
const baseStyle = {
|
||||
position: "absolute",
|
||||
top: "50%",
|
||||
height: `${height}px`,
|
||||
transform: "translateY(-50%)",
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
position: "relative",
|
||||
width: "100%",
|
||||
height: `${height}px`,
|
||||
backgroundColor,
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{/* Negative (left) bar */}
|
||||
<div
|
||||
style={{
|
||||
...baseStyle,
|
||||
right: "50%", // anchored to the center
|
||||
width: `${negativeWidth}%`,
|
||||
backgroundColor: negativeColor,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Positive (right) bar */}
|
||||
<div
|
||||
style={{
|
||||
...baseStyle,
|
||||
left: "50%", // anchored to the center
|
||||
width: `${positiveWidth}%`,
|
||||
backgroundColor: positiveColor,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
76
Assets/web/src/Components/JuceSlider.js
Normal file
76
Assets/web/src/Components/JuceSlider.js
Normal file
@ -0,0 +1,76 @@
|
||||
import PropTypes from "prop-types";
|
||||
import Box from "@mui/material/Container";
|
||||
import Typography from "@mui/material/Typography";
|
||||
|
||||
import Slider from "@mui/material/Slider";
|
||||
import * as Juce from "juce-framework-frontend";
|
||||
import { React, useState, useEffect } from "react";
|
||||
import { controlParameterIndexAnnotation } from "../types/JuceTypes.js";
|
||||
|
||||
export default function JuceSlider({ identifier, title }) {
|
||||
JuceSlider.propTypes = {
|
||||
identifier: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
};
|
||||
|
||||
const sliderState = Juce.getSliderState(identifier);
|
||||
|
||||
const [value, setValue] = useState(sliderState.getNormalisedValue());
|
||||
const [properties, setProperties] = useState(sliderState.properties);
|
||||
|
||||
const handleChange = (event, newValue) => {
|
||||
sliderState.setNormalisedValue(newValue);
|
||||
setValue(newValue);
|
||||
};
|
||||
|
||||
const mouseDown = () => {
|
||||
sliderState.sliderDragStarted();
|
||||
};
|
||||
|
||||
const changeCommitted = (event, newValue) => {
|
||||
sliderState.setNormalisedValue(newValue);
|
||||
sliderState.sliderDragEnded();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const valueListenerId = sliderState.valueChangedEvent.addListener(() => {
|
||||
setValue(sliderState.getNormalisedValue());
|
||||
});
|
||||
const propertiesListenerId = sliderState.propertiesChangedEvent.addListener(
|
||||
() => setProperties(sliderState.properties)
|
||||
);
|
||||
|
||||
return function cleanup() {
|
||||
sliderState.valueChangedEvent.removeListener(valueListenerId);
|
||||
sliderState.propertiesChangedEvent.removeListener(propertiesListenerId);
|
||||
};
|
||||
});
|
||||
|
||||
function calculateValue() {
|
||||
return sliderState.getScaledValue();
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
{...{
|
||||
[controlParameterIndexAnnotation]:
|
||||
sliderState.properties.parameterIndex,
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ mt: 1.5 }}>
|
||||
{properties.name}: {sliderState.getScaledValue()} {properties.label}
|
||||
</Typography>
|
||||
<Slider
|
||||
aria-label={title}
|
||||
value={value}
|
||||
scale={calculateValue}
|
||||
onChange={handleChange}
|
||||
min={0}
|
||||
max={1}
|
||||
step={1 / (properties.numSteps - 1)}
|
||||
onChangeCommitted={changeCommitted}
|
||||
onMouseDown={mouseDown}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@ -1,88 +0,0 @@
|
||||
import clsx from "clsx";
|
||||
import { useId, useState, React } from "react";
|
||||
import {
|
||||
KnobHeadless,
|
||||
KnobHeadlessLabel,
|
||||
KnobHeadlessOutput,
|
||||
useKnobKeyboardControls,
|
||||
} from "react-knob-headless";
|
||||
import { mapFrom01Linear, mapTo01Linear } from "@dsp-ts/math";
|
||||
import { KnobBaseThumb } from "./KnobBaseThumb";
|
||||
|
||||
export function KnobBase({
|
||||
theme,
|
||||
label,
|
||||
valueDefault,
|
||||
valueMin,
|
||||
valueMax,
|
||||
valueRawRoundFn,
|
||||
valueRawDisplayFn,
|
||||
axis,
|
||||
stepFn,
|
||||
stepLargerFn,
|
||||
mapTo01 = mapTo01Linear,
|
||||
mapFrom01 = mapFrom01Linear,
|
||||
}) {
|
||||
KnobBase.propTypes = {
|
||||
theme: KnobBase.string,
|
||||
label: KnobBase.string,
|
||||
valueDefault: KnobBase.number,
|
||||
valueMin: KnobBase.number,
|
||||
valueMax: KnobBase.number,
|
||||
valueRawRoundFn: KnobBase.func,
|
||||
valueRawDisplayFn: KnobBase.func,
|
||||
axis: KnobBase.string,
|
||||
stepFn: KnobBase.func,
|
||||
stepLargerFn: KnobBase.func,
|
||||
mapTo01: KnobBase.func,
|
||||
mapFrom01: KnobBase.func,
|
||||
};
|
||||
const knobId = useId();
|
||||
const labelId = useId();
|
||||
const [valueRaw, setValueRaw] = useState(valueDefault);
|
||||
const value01 = mapTo01(valueRaw, valueMin, valueMax);
|
||||
const step = stepFn(valueRaw);
|
||||
const stepLarger = stepLargerFn(valueRaw);
|
||||
const dragSensitivity = 0.006;
|
||||
|
||||
const keyboardControlHandlers = useKnobKeyboardControls({
|
||||
valueRaw,
|
||||
valueMin,
|
||||
valueMax,
|
||||
step,
|
||||
stepLarger,
|
||||
onValueRawChange: setValueRaw,
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"w-16 flex flex-col gap-0.5 justify-center items-center text-xs select-none",
|
||||
"outline-none focus-within:outline-1 focus-within:outline-offset-4 focus-within:outline-stone-300"
|
||||
)}
|
||||
>
|
||||
<KnobHeadlessLabel id={labelId}>{label}</KnobHeadlessLabel>
|
||||
<KnobHeadless
|
||||
id={knobId}
|
||||
aria-labelledby={labelId}
|
||||
className="relative w-16 h-16 outline-none"
|
||||
valueMin={valueMin}
|
||||
valueMax={valueMax}
|
||||
valueRaw={valueRaw}
|
||||
valueRawRoundFn={valueRawRoundFn}
|
||||
valueRawDisplayFn={valueRawDisplayFn}
|
||||
dragSensitivity={dragSensitivity}
|
||||
axis={axis}
|
||||
mapTo01={mapTo01}
|
||||
mapFrom01={mapFrom01}
|
||||
onValueRawChange={setValueRaw}
|
||||
{...keyboardControlHandlers}
|
||||
>
|
||||
<KnobBaseThumb theme={theme} value01={value01} />
|
||||
</KnobHeadless>
|
||||
<KnobHeadlessOutput htmlFor={knobId}>
|
||||
{valueRawDisplayFn(valueRaw)}
|
||||
</KnobHeadlessOutput>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
import clsx from "clsx";
|
||||
import { mapFrom01Linear } from "@dsp-ts/math";
|
||||
import { React } from "react";
|
||||
|
||||
export function KnobBaseThumb({ theme, value01 }) {
|
||||
KnobBaseThumb.propTypes = {
|
||||
theme: KnobBaseThumb.string,
|
||||
value01: KnobBaseThumb.number,
|
||||
};
|
||||
const angleMin = -145;
|
||||
const angleMax = 145;
|
||||
const angle = mapFrom01Linear(value01, angleMin, angleMax);
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"absolute h-full w-full rounded-full",
|
||||
theme === "stone" && "bg-stone-300",
|
||||
theme === "pink" && "bg-pink-300",
|
||||
theme === "green" && "bg-green-300",
|
||||
theme === "sky" && "bg-sky-300"
|
||||
)}
|
||||
>
|
||||
<div className="absolute h-full w-full" style={{ rotate: `${angle}deg` }}>
|
||||
<div className="absolute left-1/2 top-0 h-1/2 w-[2px] -translate-x-1/2 rounded-sm bg-stone-950" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -1,33 +0,0 @@
|
||||
"use client";
|
||||
import { KnobBase } from "./KnobBase";
|
||||
import { React } from "react";
|
||||
|
||||
export function KnobPercentage({ theme, label, axis }) {
|
||||
KnobPercentage.propTypes = {
|
||||
theme: KnobPercentage.string,
|
||||
label: KnobPercentage.string,
|
||||
axis: KnobPercentage.string,
|
||||
};
|
||||
return (
|
||||
<KnobBase
|
||||
valueDefault={valueDefault}
|
||||
valueMin={valueMin}
|
||||
valueMax={valueMax}
|
||||
stepFn={stepFn}
|
||||
stepLargerFn={stepLargerFn}
|
||||
valueRawRoundFn={valueRawRoundFn}
|
||||
valueRawDisplayFn={valueRawDisplayFn}
|
||||
theme={theme}
|
||||
label={label}
|
||||
axis={axis}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const valueMin = 0;
|
||||
const valueMax = 100;
|
||||
const valueDefault = 50;
|
||||
const stepFn = (valueRaw) => 1;
|
||||
const stepLargerFn = (valueRaw) => 10;
|
||||
const valueRawRoundFn = Math.round;
|
||||
const valueRawDisplayFn = (valueRaw) => `${valueRawRoundFn(valueRaw)}%`;
|
||||
69
Assets/web/src/Components/MidiNoteInfo.js
Normal file
69
Assets/web/src/Components/MidiNoteInfo.js
Normal file
@ -0,0 +1,69 @@
|
||||
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";
|
||||
|
||||
// eslint-disable-next-line react/prop-types
|
||||
|
||||
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;
|
||||
|
||||
function render() {
|
||||
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);
|
||||
}
|
||||
|
||||
window.requestAnimationFrame(render);
|
||||
return function cleanup() {
|
||||
isActiveRef.current = false;
|
||||
if (dataReceiverRef.current) dataReceiverRef.current.unregister();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div style={{ padding: "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>
|
||||
);
|
||||
}
|
||||
54
Assets/web/src/DataRecievers/MidiNoteDataReceiver.js
Normal file
54
Assets/web/src/DataRecievers/MidiNoteDataReceiver.js
Normal file
@ -0,0 +1,54 @@
|
||||
// import * as Juce from "juce-framework-frontend";
|
||||
// import reportWebVitals from "../reportWebVitals";
|
||||
|
||||
const MidNoteDataReceiver_eventId = "midNoteData";
|
||||
export default class MidNoteDataReceiver {
|
||||
constructor() {
|
||||
this.notes = [];
|
||||
this.input_pitch = 0;
|
||||
this.output_pitch = 0;
|
||||
let self = this;
|
||||
this.midNoteDataRegistrationId = window.__JUCE__.backend.addEventListener(
|
||||
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;
|
||||
// });
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
getNotes() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -25,7 +25,7 @@ import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import "./index.css";
|
||||
import App from "./App";
|
||||
import reportWebVitals from "./reportWebVitals";
|
||||
import reportWebVitals from "./reportWebVitals.js";
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById("root"));
|
||||
root.render(
|
||||
|
||||
@ -21,9 +21,9 @@
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
const reportWebVitals = onPerfEntry => {
|
||||
const reportWebVitals = (onPerfEntry) => {
|
||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
||||
import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
||||
getCLS(onPerfEntry);
|
||||
getFID(onPerfEntry);
|
||||
getFCP(onPerfEntry);
|
||||
|
||||
1
Assets/web/src/types/JuceTypes.js
Normal file
1
Assets/web/src/types/JuceTypes.js
Normal file
@ -0,0 +1 @@
|
||||
export const controlParameterIndexAnnotation = "controlparameterindex";
|
||||
@ -7,57 +7,14 @@
|
||||
#endif
|
||||
|
||||
|
||||
class MidiPitchSmoother {
|
||||
public:
|
||||
|
||||
MidiPitchSmoother() {
|
||||
tau = .1;
|
||||
dt = 2048.0f / 48000.0f;
|
||||
PcorrPrev = 60.0f;
|
||||
PtargPrev = 60;
|
||||
}
|
||||
void SetFrameTime(float frame_time) {
|
||||
dt = frame_time;
|
||||
}
|
||||
|
||||
void SetTimeConstant(float t) {
|
||||
tau = t;
|
||||
}
|
||||
float update(float Pdet, int Ptarget) {
|
||||
// Detect large jump (new note)
|
||||
float diff = std::fabs(Pdet - PcorrPrev);
|
||||
if (Ptarget != PtargPrev) {
|
||||
// Immediately reset to new note
|
||||
PcorrPrev = Pdet;
|
||||
PtargPrev = Ptarget;
|
||||
return PcorrPrev;
|
||||
}
|
||||
|
||||
// Compute smoothing coefficient
|
||||
float alpha = 1.0 - std::exp(-dt / tau);
|
||||
|
||||
// Smooth within same note
|
||||
float PcorrNew = PcorrPrev + alpha * (Ptarget - PcorrPrev);
|
||||
|
||||
PcorrPrev = PcorrNew;
|
||||
return PcorrNew;
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
|
||||
float tau; // Time constant (s)
|
||||
float dt; // Frame time (s)
|
||||
float PcorrPrev; // Previous corrected pitch (MIDI)
|
||||
int PtargPrev; // Previous corrected pitch (MIDI)
|
||||
};
|
||||
|
||||
MidiPitchSmoother out_midi_smoother;
|
||||
|
||||
void Shifter::SetAutoTuneSpeed(float val) {
|
||||
out_midi_smoother.SetTimeConstant(val);
|
||||
}
|
||||
|
||||
void Shifter::SetAutoTuneDepth(float val) {
|
||||
out_midi_smoother.SetDepth(val);
|
||||
}
|
||||
|
||||
static inline float mtof(float m)
|
||||
{
|
||||
return powf(2, (m - 69.0f) / 12.0f) * 440.0f;
|
||||
@ -145,7 +102,7 @@ void Shifter::DetectPitch(const float* const* in, float** out, size_t size)
|
||||
//DBG("frequency: " << 48000 / period << " fidel: " << fidel);
|
||||
|
||||
// Adjustable filter amount (0.0f = no filtering, 1.0f = max filtering)
|
||||
static float in_period_filter_amount = 0.0f; // You can expose this as a parameter
|
||||
static float in_period_filter_amount = 0.5f; // You can expose this as a parameter
|
||||
|
||||
if (fidel > 0.95) {
|
||||
// Smoothly filter in_period changes
|
||||
|
||||
@ -73,6 +73,70 @@ public:
|
||||
T* tail;
|
||||
};
|
||||
|
||||
|
||||
|
||||
class MidiPitchSmoother {
|
||||
public:
|
||||
|
||||
MidiPitchSmoother() {
|
||||
tau = .1;
|
||||
dt = 2048.0f / 48000.0f;
|
||||
PcorrPrev = 60.0f;
|
||||
PtargPrev = 60;
|
||||
depth = 1.0f;
|
||||
}
|
||||
void SetFrameTime(float frame_time) {
|
||||
dt = frame_time;
|
||||
}
|
||||
void SetDepth(float d) {
|
||||
// clamp to [0,1]
|
||||
if (d < 0.0f) d = 0.0f;
|
||||
if (d > 1.0f) d = 1.0f;
|
||||
depth = d;
|
||||
}
|
||||
|
||||
void SetTimeConstant(float t) {
|
||||
tau = t;
|
||||
}
|
||||
float update(float Pdet, int Ptarget) {
|
||||
// Detect large jump (new note)
|
||||
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
|
||||
float alpha = 1.0 - std::exp(-dt / tau);
|
||||
|
||||
// Compute smoothed pitch toward target
|
||||
float PcorrFull = PcorrPrev + alpha * (0 - PcorrPrev) * depth + (1 - depth) * diff;
|
||||
|
||||
// Apply depth: scale the correction amount
|
||||
inputPrev = Pdet;
|
||||
PcorrPrev = PcorrFull;
|
||||
return Ptarget + PcorrFull;
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
|
||||
float tau; // Time constant (s)
|
||||
float dt; // Frame time (s)
|
||||
float PcorrPrev; // Previous corrected pitch (MIDI)
|
||||
int PtargPrev; // Previous corrected pitch (MIDI)
|
||||
float depth; // Amount of correction applied [0..1]
|
||||
float inputPrev;
|
||||
};
|
||||
|
||||
|
||||
|
||||
class Shifter {
|
||||
public:
|
||||
void Init(float samplerate, int samplesPerBlock);
|
||||
@ -84,11 +148,14 @@ public:
|
||||
void RemoveMidiNote(int note);
|
||||
void SetFormantPreserve(float val) { formant_preserve = val; }
|
||||
void SetAutoTuneSpeed(float val);
|
||||
void SetAutoTuneDepth(float val);
|
||||
void SetPortamentoTime(float time) {
|
||||
for (int i = 0; i < MAX_VOICES; ++i) {
|
||||
voices[i].SetPortamentoTime(time);
|
||||
}
|
||||
}
|
||||
float getInputPitch() const { return sample_rate_ / in_period; }
|
||||
float getOutputPitch() const { return sample_rate_ / out_period; }
|
||||
|
||||
float out_midi = 40;
|
||||
ShifterVoice voices[MAX_VOICES];
|
||||
@ -149,5 +216,6 @@ private:
|
||||
float cos_lookup[8192];
|
||||
float sample_rate_;
|
||||
int blocksize;
|
||||
MidiPitchSmoother out_midi_smoother;
|
||||
};
|
||||
#endif
|
||||
@ -63,8 +63,9 @@ namespace ID
|
||||
#define PARAMETER_ID(str) static const ParameterID str { #str, 1 };
|
||||
|
||||
PARAMETER_ID(formantPreserve)
|
||||
PARAMETER_ID(autoTuneSpeed)
|
||||
PARAMETER_ID(portTime)
|
||||
PARAMETER_ID(autoTuneSpeed)
|
||||
PARAMETER_ID(autoTuneDepth)
|
||||
PARAMETER_ID(portTime)
|
||||
PARAMETER_ID(mute)
|
||||
PARAMETER_ID(filterType)
|
||||
|
||||
@ -215,13 +216,18 @@ public:
|
||||
autoTuneSpeed(addToLayout<AudioParameterFloat>(layout,
|
||||
ID::autoTuneSpeed,
|
||||
"AutoTune Speed",
|
||||
NormalisableRange<float> {0.001f, 0.4f, .001f},
|
||||
NormalisableRange<float> {0.001f, 0.1f, .001f},
|
||||
.5f)),
|
||||
autoTuneDepth(addToLayout<AudioParameterFloat>(layout,
|
||||
ID::autoTuneDepth,
|
||||
"AutoTune Depth",
|
||||
NormalisableRange<float> {0.0f, 1.1f, .01f},
|
||||
.5f)),
|
||||
portTime(addToLayout<AudioParameterFloat>(layout,
|
||||
ID::portTime,
|
||||
"Portamento Speed",
|
||||
NormalisableRange<float> {0.001f, 0.2f, .001f},
|
||||
.001f)),
|
||||
.01f)),
|
||||
mute(addToLayout<AudioParameterBool>(layout, ID::mute, "Mute", false)),
|
||||
filterType(addToLayout<AudioParameterChoice>(layout,
|
||||
ID::filterType,
|
||||
@ -233,6 +239,7 @@ public:
|
||||
|
||||
AudioParameterFloat& formantPreserve;
|
||||
AudioParameterFloat& autoTuneSpeed;
|
||||
AudioParameterFloat& autoTuneDepth;
|
||||
AudioParameterFloat& portTime;
|
||||
AudioParameterBool& mute;
|
||||
AudioParameterChoice& filterType;
|
||||
@ -330,6 +337,7 @@ void WebViewPluginAudioProcessor::processBlock(juce::AudioBuffer<float>& buffer,
|
||||
buffer.clear(i, 0, buffer.getNumSamples());
|
||||
shifter.SetFormantPreserve(parameters.formantPreserve.get());
|
||||
shifter.SetAutoTuneSpeed(parameters.autoTuneSpeed.get());
|
||||
shifter.SetAutoTuneDepth(parameters.autoTuneDepth.get());
|
||||
shifter.SetPortamentoTime(parameters.portTime.get());
|
||||
juce::AudioBuffer<float> const_buff;
|
||||
const_buff.makeCopyOf(buffer);
|
||||
@ -459,29 +467,25 @@ public:
|
||||
|
||||
SpinLock::ScopedLockType lock{ processorRef.midiLock };
|
||||
|
||||
/*Array<var> frame;
|
||||
|
||||
for (size_t i = 1; i < processorRef.spectrumData.size(); ++i)
|
||||
frame.add(processorRef.spectrumData[i]);
|
||||
|
||||
spectrumDataFrames.clear();
|
||||
|
||||
spectrumDataFrames.push_back(std::move(frame));
|
||||
|
||||
while (spectrumDataFrames.size() > numFramesBuffered)
|
||||
spectrumDataFrames.pop_front();*/
|
||||
|
||||
static int64 callbackCounter = 0;
|
||||
|
||||
/*if ( spectrumDataFrames.size() == numFramesBuffered
|
||||
&& callbackCounter++ % (int64) numFramesBuffered)
|
||||
{*/
|
||||
if (processorRef.new_midi) {
|
||||
processorRef.new_midi = false;
|
||||
webComponent.emitEventIfBrowserIsVisible("midNoteData", var{});
|
||||
processorRef.new_midi = false;
|
||||
juce::Array<var> notes;
|
||||
int voice_num = 0;
|
||||
for (auto& voice : processorRef.shifter.voices) {
|
||||
if (voice.onoff_) {
|
||||
auto obj = new DynamicObject();
|
||||
obj->setProperty("voice", voice_num);
|
||||
obj->setProperty("midi", voice.GetMidiNote());
|
||||
notes.add(var(obj));
|
||||
}
|
||||
voice_num++;
|
||||
}
|
||||
|
||||
//}
|
||||
DynamicObject::Ptr d(new DynamicObject());
|
||||
d->setProperty("notes", notes);
|
||||
d->setProperty("input_pitch", processorRef.shifter.getInputPitch());
|
||||
d->setProperty("output_pitch", processorRef.shifter.getOutputPitch());
|
||||
webComponent.emitEventIfBrowserIsVisible("midNoteData", d.get());
|
||||
}
|
||||
|
||||
private:
|
||||
@ -489,6 +493,7 @@ private:
|
||||
|
||||
WebSliderRelay formantSliderRelay{ "formantSlider" };
|
||||
WebSliderRelay autoTuneSpeedSliderRelay{ "autoTuneSpeedSlider" };
|
||||
WebSliderRelay autoTuneDepthSliderRelay{ "autoTuneDepthSlider" };
|
||||
WebSliderRelay portTimeSliderRelay{ "portTimeSlider" };
|
||||
WebToggleButtonRelay muteToggleRelay{ "muteToggle" };
|
||||
WebComboBoxRelay filterTypeComboRelay{ "filterTypeCombo" };
|
||||
@ -502,6 +507,7 @@ private:
|
||||
.withNativeIntegrationEnabled()
|
||||
.withOptionsFrom(formantSliderRelay)
|
||||
.withOptionsFrom(autoTuneSpeedSliderRelay)
|
||||
.withOptionsFrom(autoTuneDepthSliderRelay)
|
||||
.withOptionsFrom(portTimeSliderRelay)
|
||||
.withOptionsFrom(muteToggleRelay)
|
||||
.withOptionsFrom(filterTypeComboRelay)
|
||||
@ -518,6 +524,7 @@ private:
|
||||
|
||||
WebSliderParameterAttachment formantAttachment;
|
||||
WebSliderParameterAttachment autoTuneSpeedAttachment;
|
||||
WebSliderParameterAttachment autoTuneDepthAttachment;
|
||||
WebSliderParameterAttachment portTimeAttachment;
|
||||
WebToggleButtonParameterAttachment muteAttachment;
|
||||
WebComboBoxParameterAttachment filterTypeAttachment;
|
||||
@ -612,6 +619,7 @@ std::optional<WebBrowserComponent::Resource> WebViewPluginAudioProcessorEditor::
|
||||
|
||||
if (urlToRetrive == "midNoteData.json")
|
||||
{
|
||||
|
||||
juce::Array<var> notes;
|
||||
int voice_num = 0;
|
||||
for (auto& voice : processorRef.shifter.voices) {
|
||||
@ -656,6 +664,9 @@ WebViewPluginAudioProcessorEditor::WebViewPluginAudioProcessorEditor(WebViewPlug
|
||||
autoTuneSpeedAttachment(*processorRef.state.getParameter(ID::autoTuneSpeed.getParamID()),
|
||||
autoTuneSpeedSliderRelay,
|
||||
processorRef.state.undoManager),
|
||||
autoTuneDepthAttachment(*processorRef.state.getParameter(ID::autoTuneDepth.getParamID()),
|
||||
autoTuneDepthSliderRelay,
|
||||
processorRef.state.undoManager),
|
||||
portTimeAttachment(*processorRef.state.getParameter(ID::portTime.getParamID()),
|
||||
portTimeSliderRelay,
|
||||
processorRef.state.undoManager),
|
||||
|
||||
Reference in New Issue
Block a user