midi notes avaiable on ui
This commit is contained in:
@ -27,21 +27,22 @@ import "@fontsource/roboto/500.css";
|
|||||||
import "@fontsource/roboto/700.css";
|
import "@fontsource/roboto/700.css";
|
||||||
|
|
||||||
import Box from "@mui/material/Container";
|
import Box from "@mui/material/Container";
|
||||||
import Checkbox from "@mui/material/Checkbox";
|
// import Checkbox from "@mui/material/Checkbox";
|
||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
import Container from "@mui/material/Container";
|
import Container from "@mui/material/Container";
|
||||||
import Slider from "@mui/material/Slider";
|
import Slider from "@mui/material/Slider";
|
||||||
import Button from "@mui/material/Button";
|
// import Button from "@mui/material/Button";
|
||||||
import CardActions from "@mui/material/CardActions";
|
// import CardActions from "@mui/material/CardActions";
|
||||||
import Snackbar from "@mui/material/Snackbar";
|
// import Snackbar from "@mui/material/Snackbar";
|
||||||
import IconButton from "@mui/material/IconButton";
|
// import IconButton from "@mui/material/IconButton";
|
||||||
import CloseIcon from "@mui/icons-material/Close";
|
// import CloseIcon from "@mui/icons-material/Close";
|
||||||
import InputLabel from "@mui/material/InputLabel";
|
// import InputLabel from "@mui/material/InputLabel";
|
||||||
import MenuItem from "@mui/material/MenuItem";
|
// import MenuItem from "@mui/material/MenuItem";
|
||||||
import FormControl from "@mui/material/FormControl";
|
// import FormControl from "@mui/material/FormControl";
|
||||||
import Select from "@mui/material/Select";
|
// import Select from "@mui/material/Select";
|
||||||
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 PianoKeyboard from "./Components/PianoKeyboard";
|
||||||
|
|
||||||
import { React, useState, useEffect, useRef } from "react";
|
import { React, useState, useEffect, useRef } from "react";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
@ -122,262 +123,323 @@ function JuceSlider({ identifier, title }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function JuceCheckbox({ identifier }) {
|
// function JuceCheckbox({ identifier }) {
|
||||||
JuceCheckbox.propTypes = {
|
// JuceCheckbox.propTypes = {
|
||||||
identifier: PropTypes.string,
|
// identifier: PropTypes.string,
|
||||||
};
|
// };
|
||||||
|
|
||||||
const checkboxState = Juce.getToggleState(identifier);
|
// const checkboxState = Juce.getToggleState(identifier);
|
||||||
|
|
||||||
const [value, setValue] = useState(checkboxState.getValue());
|
// const [value, setValue] = useState(checkboxState.getValue());
|
||||||
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);
|
||||||
};
|
// };
|
||||||
|
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
const valueListenerId = checkboxState.valueChangedEvent.addListener(() => {
|
// const valueListenerId = checkboxState.valueChangedEvent.addListener(() => {
|
||||||
setValue(checkboxState.getValue());
|
// setValue(checkboxState.getValue());
|
||||||
});
|
// });
|
||||||
const propertiesListenerId =
|
// const propertiesListenerId =
|
||||||
checkboxState.propertiesChangedEvent.addListener(() =>
|
// checkboxState.propertiesChangedEvent.addListener(() =>
|
||||||
setProperties(checkboxState.properties)
|
// setProperties(checkboxState.properties)
|
||||||
);
|
// );
|
||||||
|
|
||||||
return function cleanup() {
|
// return function cleanup() {
|
||||||
checkboxState.valueChangedEvent.removeListener(valueListenerId);
|
// checkboxState.valueChangedEvent.removeListener(valueListenerId);
|
||||||
checkboxState.propertiesChangedEvent.removeListener(propertiesListenerId);
|
// checkboxState.propertiesChangedEvent.removeListener(propertiesListenerId);
|
||||||
};
|
// };
|
||||||
});
|
// });
|
||||||
|
|
||||||
const cb = <Checkbox checked={value} onChange={handleChange} />;
|
// const cb = <Checkbox checked={value} onChange={handleChange} />;
|
||||||
|
|
||||||
return (
|
// return (
|
||||||
<Box
|
// <Box
|
||||||
{...{
|
// {...{
|
||||||
[controlParameterIndexAnnotation]:
|
// [controlParameterIndexAnnotation]:
|
||||||
checkboxState.properties.parameterIndex,
|
// checkboxState.properties.parameterIndex,
|
||||||
}}
|
// }}
|
||||||
>
|
// >
|
||||||
<FormGroup>
|
// <FormGroup>
|
||||||
<FormControlLabel control={cb} label={properties.name} />
|
// <FormControlLabel control={cb} label={properties.name} />
|
||||||
</FormGroup>
|
// </FormGroup>
|
||||||
</Box>
|
// </Box>
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
function JuceComboBox({ identifier }) {
|
// function JuceComboBox({ identifier }) {
|
||||||
JuceComboBox.propTypes = {
|
// JuceComboBox.propTypes = {
|
||||||
identifier: PropTypes.string,
|
// identifier: PropTypes.string,
|
||||||
};
|
// };
|
||||||
|
|
||||||
const comboBoxState = Juce.getComboBoxState(identifier);
|
// const comboBoxState = Juce.getComboBoxState(identifier);
|
||||||
|
|
||||||
const [value, setValue] = useState(comboBoxState.getChoiceIndex());
|
// const [value, setValue] = useState(comboBoxState.getChoiceIndex());
|
||||||
const [properties, setProperties] = useState(comboBoxState.properties);
|
// const [properties, setProperties] = useState(comboBoxState.properties);
|
||||||
|
|
||||||
const handleChange = (event) => {
|
// const handleChange = (event) => {
|
||||||
comboBoxState.setChoiceIndex(event.target.value);
|
// comboBoxState.setChoiceIndex(event.target.value);
|
||||||
setValue(event.target.value);
|
// setValue(event.target.value);
|
||||||
};
|
// };
|
||||||
|
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
const valueListenerId = comboBoxState.valueChangedEvent.addListener(() => {
|
// const valueListenerId = comboBoxState.valueChangedEvent.addListener(() => {
|
||||||
setValue(comboBoxState.getChoiceIndex());
|
// setValue(comboBoxState.getChoiceIndex());
|
||||||
});
|
// });
|
||||||
const propertiesListenerId =
|
// const propertiesListenerId =
|
||||||
comboBoxState.propertiesChangedEvent.addListener(() => {
|
// comboBoxState.propertiesChangedEvent.addListener(() => {
|
||||||
setProperties(comboBoxState.properties);
|
// setProperties(comboBoxState.properties);
|
||||||
});
|
// });
|
||||||
|
|
||||||
return function cleanup() {
|
// return function cleanup() {
|
||||||
comboBoxState.valueChangedEvent.removeListener(valueListenerId);
|
// comboBoxState.valueChangedEvent.removeListener(valueListenerId);
|
||||||
comboBoxState.propertiesChangedEvent.removeListener(propertiesListenerId);
|
// comboBoxState.propertiesChangedEvent.removeListener(propertiesListenerId);
|
||||||
};
|
// };
|
||||||
});
|
// });
|
||||||
|
|
||||||
return (
|
// return (
|
||||||
<Box
|
// <Box
|
||||||
{...{
|
// {...{
|
||||||
[controlParameterIndexAnnotation]:
|
// [controlParameterIndexAnnotation]:
|
||||||
comboBoxState.properties.parameterIndex,
|
// comboBoxState.properties.parameterIndex,
|
||||||
}}
|
// }}
|
||||||
>
|
// >
|
||||||
<FormControl fullWidth>
|
// <FormControl fullWidth>
|
||||||
<InputLabel id={identifier}>{properties.name}</InputLabel>
|
// <InputLabel id={identifier}>{properties.name}</InputLabel>
|
||||||
<Select
|
// <Select
|
||||||
labelId={identifier}
|
// labelId={identifier}
|
||||||
value={value}
|
// value={value}
|
||||||
label={properties.name}
|
// label={properties.name}
|
||||||
onChange={handleChange}
|
// onChange={handleChange}
|
||||||
>
|
// >
|
||||||
{properties.choices.map((choice, i) => (
|
// {properties.choices.map((choice, i) => (
|
||||||
<MenuItem value={i} key={i}>
|
// <MenuItem value={i} key={i}>
|
||||||
{choice}
|
// {choice}
|
||||||
</MenuItem>
|
// </MenuItem>
|
||||||
))}
|
// ))}
|
||||||
</Select>
|
// </Select>
|
||||||
</FormControl>
|
// </FormControl>
|
||||||
</Box>
|
// </Box>
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
const sayHello = Juce.getNativeFunction("sayHello");
|
// const sayHello = Juce.getNativeFunction("sayHello");
|
||||||
|
|
||||||
const SpectrumDataReceiver_eventId = "spectrumData";
|
// const SpectrumDataReceiver_eventId = "spectrumData";
|
||||||
|
|
||||||
function interpolate(a, b, s) {
|
// function interpolate(a, b, s) {
|
||||||
let result = new Array(a.length).fill(0);
|
// 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 a.entries()) result[i] += (1 - s) * val;
|
||||||
|
|
||||||
for (const [i, val] of b.entries()) result[i] += s * val;
|
// for (const [i, val] of b.entries()) result[i] += s * val;
|
||||||
|
|
||||||
return result;
|
// return result;
|
||||||
}
|
// }
|
||||||
|
|
||||||
function mod(dividend, divisor) {
|
// function mod(dividend, divisor) {
|
||||||
const quotient = Math.floor(dividend / divisor);
|
// const quotient = Math.floor(dividend / divisor);
|
||||||
return dividend - divisor * quotient;
|
// return dividend - divisor * quotient;
|
||||||
}
|
// }
|
||||||
|
|
||||||
class SpectrumDataReceiver {
|
// class SpectrumDataReceiver {
|
||||||
constructor(bufferLength) {
|
// constructor(bufferLength) {
|
||||||
this.bufferLength = bufferLength;
|
// this.bufferLength = bufferLength;
|
||||||
this.buffer = new Array(this.bufferLength);
|
// this.buffer = new Array(this.bufferLength);
|
||||||
this.readIndex = 0;
|
// this.readIndex = 0;
|
||||||
this.writeIndex = 0;
|
// this.writeIndex = 0;
|
||||||
this.lastTimeStampMs = 0;
|
// this.lastTimeStampMs = 0;
|
||||||
this.timeResolutionMs = 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;
|
let self = this;
|
||||||
this.spectrumDataRegistrationId = window.__JUCE__.backend.addEventListener(
|
this.midNoteDataRegistrationId = window.__JUCE__.backend.addEventListener(
|
||||||
SpectrumDataReceiver_eventId,
|
MidNoteDataReceiver_eventId,
|
||||||
() => {
|
() => {
|
||||||
fetch(Juce.getBackendResourceAddress("spectrumData.json"))
|
fetch(Juce.getBackendResourceAddress("midNoteData.json"))
|
||||||
.then((response) => response.text())
|
.then((response) => response.text())
|
||||||
.then((text) => {
|
.then((text) => {
|
||||||
const data = JSON.parse(text);
|
const data = JSON.parse(text);
|
||||||
|
self.notes = data.notes;
|
||||||
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) {
|
getNotes() {
|
||||||
return this.buffer[mod(index, this.buffer.length)];
|
return this.notes;
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
unregister() {
|
||||||
window.__JUCE__.backend.removeEventListener(
|
window.__JUCE__.backend.removeEventListener(this.midNoteDataRegistrationId);
|
||||||
this.spectrumDataRegistrationId
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function FreqBandInfo() {
|
function MidiNoteInfo() {
|
||||||
const canvasRef = useRef(null);
|
const [notes, setNotes] = useState([]);
|
||||||
let dataReceiver = null;
|
const dataReceiverRef = useRef(null);
|
||||||
let isActive = true;
|
const isActiveRef = useRef(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(() => {
|
useEffect(() => {
|
||||||
dataReceiver = new SpectrumDataReceiver(10);
|
dataReceiverRef.current = new MidNoteDataReceiver();
|
||||||
isActive = true;
|
isActiveRef.current = true;
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
if (dataReceiverRef.current) {
|
||||||
|
setNotes(dataReceiverRef.current.getNotes());
|
||||||
|
}
|
||||||
|
if (isActiveRef.current) {
|
||||||
|
window.requestAnimationFrame(render);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
window.requestAnimationFrame(render);
|
window.requestAnimationFrame(render);
|
||||||
|
|
||||||
return function cleanup() {
|
return function cleanup() {
|
||||||
isActive = false;
|
isActiveRef.current = false;
|
||||||
dataReceiver.unregister();
|
if (dataReceiverRef.current) {
|
||||||
};
|
dataReceiverRef.current.unregister();
|
||||||
});
|
}
|
||||||
|
|
||||||
const canvasStyle = {
|
|
||||||
marginLeft: "0",
|
|
||||||
marginRight: "0",
|
|
||||||
marginTop: "1em",
|
|
||||||
display: "block",
|
|
||||||
width: "94%",
|
|
||||||
bottom: "0",
|
|
||||||
position: "absolute",
|
|
||||||
};
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<div>
|
||||||
<canvas height={90} style={canvasStyle} ref={canvasRef}></canvas>
|
<PianoKeyboard heldNotes={notes} />
|
||||||
</Box>
|
</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() {
|
function App() {
|
||||||
const controlParameterIndexUpdater = new Juce.ControlParameterIndexUpdater(
|
const controlParameterIndexUpdater = new Juce.ControlParameterIndexUpdater(
|
||||||
controlParameterIndexAnnotation
|
controlParameterIndexAnnotation
|
||||||
@ -387,33 +449,33 @@ function App() {
|
|||||||
controlParameterIndexUpdater.handleMouseMove(event);
|
controlParameterIndexUpdater.handleMouseMove(event);
|
||||||
});
|
});
|
||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
// const [open, setOpen] = useState(false);
|
||||||
const [snackbarMessage, setMessage] = useState("No message received yet");
|
// const [snackbarMessage, setMessage] = useState("No message received yet");
|
||||||
|
|
||||||
const openSnackbar = () => {
|
// const openSnackbar = () => {
|
||||||
setOpen(true);
|
// setOpen(true);
|
||||||
};
|
// };
|
||||||
|
|
||||||
const handleClose = (event, reason) => {
|
// const handleClose = (event, reason) => {
|
||||||
if (reason === "clickaway") {
|
// if (reason === "clickaway") {
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
setOpen(false);
|
// setOpen(false);
|
||||||
};
|
// };
|
||||||
|
|
||||||
const action = (
|
// const action = (
|
||||||
<>
|
// <>
|
||||||
<IconButton
|
// <IconButton
|
||||||
size="small"
|
// size="small"
|
||||||
aria-label="close"
|
// aria-label="close"
|
||||||
color="inherit"
|
// color="inherit"
|
||||||
onClick={handleClose}
|
// onClick={handleClose}
|
||||||
>
|
// >
|
||||||
<CloseIcon fontSize="small" />
|
// <CloseIcon fontSize="small" />
|
||||||
</IconButton>
|
// </IconButton>
|
||||||
</>
|
// </>
|
||||||
);
|
// );
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@ -422,7 +484,8 @@ function App() {
|
|||||||
<JuceSlider identifier="autoTuneSpeedSlider" title="Auto Tune Speed" />
|
<JuceSlider identifier="autoTuneSpeedSlider" title="Auto Tune Speed" />
|
||||||
<JuceSlider identifier="portTimeSlider" title="Portamento Speed" />
|
<JuceSlider identifier="portTimeSlider" title="Portamento Speed" />
|
||||||
</Container>
|
</Container>
|
||||||
<CardActions style={{ justifyContent: "center" }}>
|
<MidiNoteInfo />
|
||||||
|
{/* <CardActions style={{ justifyContent: "center" }}>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
sx={{ marginTop: 2 }}
|
sx={{ marginTop: 2 }}
|
||||||
@ -454,15 +517,15 @@ function App() {
|
|||||||
</CardActions>
|
</CardActions>
|
||||||
<JuceCheckbox identifier="muteToggle" />
|
<JuceCheckbox identifier="muteToggle" />
|
||||||
<br></br>
|
<br></br>
|
||||||
<JuceComboBox identifier="filterTypeCombo" />
|
<JuceComboBox identifier="filterTypeCombo" /> */}
|
||||||
<FreqBandInfo></FreqBandInfo>
|
|
||||||
<Snackbar
|
{/* <Snackbar
|
||||||
open={open}
|
open={open}
|
||||||
autoHideDuration={6000}
|
autoHideDuration={6000}
|
||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
message={snackbarMessage}
|
message={snackbarMessage}
|
||||||
action={action}
|
action={action}
|
||||||
/>
|
/> */}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
203
Assets/web/src/Components/PianoKeyboard.js
Normal file
203
Assets/web/src/Components/PianoKeyboard.js
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
/* eslint-disable react/prop-types */
|
||||||
|
import React /*, { useRef, useEffect, useState }*/ from "react";
|
||||||
|
|
||||||
|
const NOTE_NAMES = [
|
||||||
|
"C",
|
||||||
|
"C♯",
|
||||||
|
"D",
|
||||||
|
"D♯",
|
||||||
|
"E",
|
||||||
|
"F",
|
||||||
|
"F♯",
|
||||||
|
"G",
|
||||||
|
"G♯",
|
||||||
|
"A",
|
||||||
|
"A♯",
|
||||||
|
"B",
|
||||||
|
];
|
||||||
|
const WHITE_KEYS = [0, 2, 4, 5, 7, 9, 11];
|
||||||
|
|
||||||
|
// C2 = 36, C5 = 72
|
||||||
|
const LOWEST_MIDI = 36;
|
||||||
|
const HIGHEST_MIDI = 72;
|
||||||
|
|
||||||
|
function getNoteName(midi) {
|
||||||
|
const octave = Math.floor(midi / 12) - 1;
|
||||||
|
const note = NOTE_NAMES[midi % 12];
|
||||||
|
return `${note}${octave}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isWhiteKey(midi) {
|
||||||
|
return WHITE_KEYS.includes(midi % 12);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PianoKeyboard({ heldNotes }) {
|
||||||
|
const heldMap = {};
|
||||||
|
heldNotes.forEach((n) => (heldMap[n.midi] = n.voice));
|
||||||
|
|
||||||
|
const keys = [];
|
||||||
|
for (let midi = LOWEST_MIDI; midi <= HIGHEST_MIDI; midi++) {
|
||||||
|
const white = isWhiteKey(midi);
|
||||||
|
const held = heldMap[midi] !== undefined;
|
||||||
|
keys.push({
|
||||||
|
midi,
|
||||||
|
white,
|
||||||
|
held,
|
||||||
|
voice: heldMap[midi],
|
||||||
|
noteName: getNoteName(midi),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const whiteKeys = keys.filter((k) => k.white);
|
||||||
|
const blackKeys = keys.filter((k) => !k.white);
|
||||||
|
|
||||||
|
// For responsive black key positioning
|
||||||
|
const numWhite = whiteKeys.length;
|
||||||
|
|
||||||
|
// Map midi to white key index for black key positioning
|
||||||
|
const midiToWhiteIndex = {};
|
||||||
|
let whiteIdx = 0;
|
||||||
|
for (let midi = LOWEST_MIDI; midi <= HIGHEST_MIDI; midi++) {
|
||||||
|
if (isWhiteKey(midi)) {
|
||||||
|
midiToWhiteIndex[midi] = whiteIdx++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For each black key, find its position between white keys
|
||||||
|
function getBlackKeyPercent(midi) {
|
||||||
|
// Black keys are always after a white key except for the first key
|
||||||
|
// For example, C# is between C and D
|
||||||
|
// So, find the previous white key index, then add ~0.65 of a white key width
|
||||||
|
const prevWhite = midi - 1;
|
||||||
|
const idx = midiToWhiteIndex[prevWhite];
|
||||||
|
if (idx === undefined) return 0;
|
||||||
|
// Offset: (idx + 0.65) / numWhite * 100%
|
||||||
|
return ((idx + 0.65) / numWhite) * 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "relative",
|
||||||
|
height: 80,
|
||||||
|
userSelect: "none",
|
||||||
|
width: "100%",
|
||||||
|
minWidth: 200,
|
||||||
|
maxWidth: 900,
|
||||||
|
margin: "0 auto",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* White keys */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
position: "relative",
|
||||||
|
zIndex: 1,
|
||||||
|
height: "100%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{whiteKeys.map((k) => (
|
||||||
|
<div
|
||||||
|
key={k.midi}
|
||||||
|
style={{
|
||||||
|
flex: "1 1 0",
|
||||||
|
height: "100%",
|
||||||
|
border: "1px solid #888",
|
||||||
|
background: k.held ? "#2a82caff" : "#fff",
|
||||||
|
position: "relative",
|
||||||
|
boxSizing: "border-box",
|
||||||
|
marginRight: -1,
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column-reverse",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "flex-start",
|
||||||
|
fontSize: 10,
|
||||||
|
fontFamily: "monospace",
|
||||||
|
overflow: "hidden",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column-reverse",
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span style={{ opacity: 0.5 }}>{k.noteName}</span>
|
||||||
|
{k.held && (
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
color: "#fff",
|
||||||
|
fontWeight: "bold",
|
||||||
|
fontSize: 14,
|
||||||
|
lineHeight: "14px",
|
||||||
|
marginBottom: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{k.voice}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{/* Black keys */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
height: "62%",
|
||||||
|
width: "100%",
|
||||||
|
zIndex: 2,
|
||||||
|
pointerEvents: "none",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{blackKeys.map((k) => (
|
||||||
|
<div
|
||||||
|
key={k.midi}
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
left: `${getBlackKeyPercent(k.midi)}%`,
|
||||||
|
width: `${(100 / numWhite) * 0.65}%`,
|
||||||
|
height: "100%",
|
||||||
|
background: k.held ? "#2a82caff" : "#222",
|
||||||
|
border: "1px solid #444",
|
||||||
|
borderRadius: 3,
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column-reverse",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "flex-start",
|
||||||
|
fontSize: 10,
|
||||||
|
fontFamily: "monospace",
|
||||||
|
boxSizing: "border-box",
|
||||||
|
transform: "translateX(-50%)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column-reverse",
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{k.held && (
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
color: "#fff",
|
||||||
|
fontWeight: "bold",
|
||||||
|
fontSize: 14,
|
||||||
|
lineHeight: "14px",
|
||||||
|
marginBottom: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{k.voice}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -67,14 +67,17 @@ static inline bool float_equal(float one, float two) {
|
|||||||
return abs(one - two) < 1e-5f;
|
return abs(one - two) < 1e-5f;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Shifter::Init()
|
void Shifter::Init(float samplerate, int samplesPerBlock)
|
||||||
{
|
{
|
||||||
|
sample_rate_ = samplerate;
|
||||||
|
blocksize = samplesPerBlock;
|
||||||
|
out_midi_smoother.SetFrameTime((float)samplesPerBlock / samplerate);
|
||||||
volume = 1;
|
volume = 1;
|
||||||
helm.setframesize(1024);
|
helm.setframesize(1024);
|
||||||
helm.setoverlap(1);
|
helm.setoverlap(1);
|
||||||
for (int i = 0; i < MAX_VOICES; ++i)
|
for (int i = 0; i < MAX_VOICES; ++i)
|
||||||
{
|
{
|
||||||
voices[i].Init(48000);
|
voices[i].Init(samplerate);
|
||||||
}
|
}
|
||||||
for (int i = 0; i < BUFFER_SIZE; ++i)
|
for (int i = 0; i < BUFFER_SIZE; ++i)
|
||||||
{
|
{
|
||||||
@ -148,13 +151,13 @@ void Shifter::DetectPitch(const float* const* in, float** out, size_t size)
|
|||||||
// Smoothly filter in_period changes
|
// Smoothly filter in_period changes
|
||||||
in_period = in_period * in_period_filter_amount + period * (1.0f - in_period_filter_amount);
|
in_period = in_period * in_period_filter_amount + period * (1.0f - in_period_filter_amount);
|
||||||
}
|
}
|
||||||
float in_freq = 48000 / in_period;
|
float in_freq = sample_rate_ / in_period;
|
||||||
|
|
||||||
float midi = (12 * log2f(in_freq / 440) + 69.0f);
|
float midi = (12 * log2f(in_freq / 440) + 69.0f);
|
||||||
|
|
||||||
//target_out_period = in_period * out_period_filter_amount + target_out_period * (1 - out_period_filter_amount);
|
//target_out_period = in_period * out_period_filter_amount + target_out_period * (1 - out_period_filter_amount);
|
||||||
out_midi = out_midi_smoother.update(midi, (int)(midi+.5));
|
out_midi = out_midi_smoother.update(midi, (int)(midi+.5));
|
||||||
out_period = 48000.0f / mtof(out_midi);
|
out_period = sample_rate_ / mtof(out_midi);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Shifter::SetRates() {}
|
void Shifter::SetRates() {}
|
||||||
@ -275,8 +278,8 @@ void Shifter::GetSamples(float** output, const float* input, size_t size)
|
|||||||
//add new samples if necessary
|
//add new samples if necessary
|
||||||
for (int out_p = 0; out_p < MAX_VOICES; ++out_p)
|
for (int out_p = 0; out_p < MAX_VOICES; ++out_p)
|
||||||
{
|
{
|
||||||
if (!voices[out_p].IsActive()) continue;
|
|
||||||
voices[out_p].Process();
|
voices[out_p].Process();
|
||||||
|
if (!voices[out_p].IsActive()) continue;
|
||||||
if (voices[out_p].PeriodOverflow())
|
if (voices[out_p].PeriodOverflow())
|
||||||
{
|
{
|
||||||
float resampling_period = GetOutputEnvelopePeriod(out_p);
|
float resampling_period = GetOutputEnvelopePeriod(out_p);
|
||||||
@ -328,12 +331,12 @@ void Shifter::GetSamples(float** output, const float* input, size_t size)
|
|||||||
|
|
||||||
void Shifter::AddMidiNote(int note) {
|
void Shifter::AddMidiNote(int note) {
|
||||||
for (int i = 0; i < MAX_VOICES; ++i) {
|
for (int i = 0; i < MAX_VOICES; ++i) {
|
||||||
if (voices[i].IsActive() && voices[i].GetMidiNote() == note) {
|
if (voices[i].onoff_ && voices[i].GetMidiNote() == note) {
|
||||||
return;
|
voices[i].Release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (int i = 0; i < MAX_VOICES; ++i) {
|
for (int i = 0; i < MAX_VOICES; ++i) {
|
||||||
if (!voices[i].IsActive()) {
|
if (!voices[i].onoff_) {
|
||||||
voices[i].Trigger(note);
|
voices[i].Trigger(note);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -343,9 +346,8 @@ void Shifter::AddMidiNote(int note) {
|
|||||||
|
|
||||||
void Shifter::RemoveMidiNote(int note) {
|
void Shifter::RemoveMidiNote(int note) {
|
||||||
for (int i = 0; i < MAX_VOICES; ++i) {
|
for (int i = 0; i < MAX_VOICES; ++i) {
|
||||||
if (voices[i].IsActive() && voices[i].GetMidiNote() == note) {
|
if (voices[i].GetMidiNote() == note) {
|
||||||
voices[i].Release();
|
voices[i].Release();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -75,7 +75,7 @@ public:
|
|||||||
|
|
||||||
class Shifter {
|
class Shifter {
|
||||||
public:
|
public:
|
||||||
void Init();
|
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);
|
||||||
@ -91,6 +91,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
float out_midi = 40;
|
float out_midi = 40;
|
||||||
|
ShifterVoice voices[MAX_VOICES];
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void DetectPitch(const float* const* in, float** out, size_t size);
|
void DetectPitch(const float* const* in, float** out, size_t size);
|
||||||
@ -140,11 +141,13 @@ private:
|
|||||||
|
|
||||||
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
|
||||||
|
|
||||||
ShifterVoice voices[MAX_VOICES];
|
|
||||||
|
|
||||||
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_;
|
||||||
|
int blocksize;
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
@ -145,26 +145,26 @@ private:
|
|||||||
int64 writeIx = 0;
|
int64 writeIx = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class SpectralBars
|
//class SpectralBars
|
||||||
{
|
//{
|
||||||
public:
|
//public:
|
||||||
//template <typename T>
|
// //template <typename T>
|
||||||
void push(int data)
|
// void push(int data)
|
||||||
{
|
// {
|
||||||
testQueue.push(data);
|
// testQueue.push(data);
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
void compute(Span<int> output) {
|
// void compute(Span<int> output) {
|
||||||
int index = 0;
|
// int index = 0;
|
||||||
for (auto it = output.begin(); it != output.end(); ++it) {
|
// for (auto it = output.begin(); it != output.end(); ++it) {
|
||||||
*it = testQueue.get(index++);
|
// *it = testQueue.get(index++);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
private:
|
//private:
|
||||||
circ_queue<int, 256> testQueue;
|
// circ_queue<int, 256> testQueue;
|
||||||
};
|
//};
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
class WebViewPluginAudioProcessor : public AudioProcessor
|
class WebViewPluginAudioProcessor : public AudioProcessor
|
||||||
@ -200,6 +200,7 @@ public:
|
|||||||
//==============================================================================
|
//==============================================================================
|
||||||
void getStateInformation(MemoryBlock& destData) override;
|
void getStateInformation(MemoryBlock& destData) override;
|
||||||
void setStateInformation(const void* data, int sizeInBytes) override;
|
void setStateInformation(const void* data, int sizeInBytes) override;
|
||||||
|
bool new_midi = false;
|
||||||
|
|
||||||
struct Parameters
|
struct Parameters
|
||||||
{
|
{
|
||||||
@ -261,18 +262,20 @@ public:
|
|||||||
|
|
||||||
Parameters parameters;
|
Parameters parameters;
|
||||||
AudioProcessorValueTreeState state;
|
AudioProcessorValueTreeState state;
|
||||||
|
SpinLock midiLock;
|
||||||
|
|
||||||
std::vector<int> spectrumData = [] { return std::vector<int>(256, 0.0f); }();
|
/*std::vector<int> spectrumData = [] { return std::vector<int>(256, 0.0f); }();
|
||||||
SpinLock spectrumDataLock;
|
SpinLock spectrumDataLock;
|
||||||
|
|
||||||
SpectralBars spectralBars;
|
SpectralBars spectralBars;*/
|
||||||
|
|
||||||
dsp::LadderFilter<float> filter;
|
dsp::LadderFilter<float> filter;
|
||||||
|
Shifter shifter;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(WebViewPluginAudioProcessor)
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(WebViewPluginAudioProcessor)
|
||||||
Shifter shifter;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
@ -288,14 +291,14 @@ WebViewPluginAudioProcessor::WebViewPluginAudioProcessor(AudioProcessorValueTree
|
|||||||
parameters(layout),
|
parameters(layout),
|
||||||
state(*this, nullptr, "STATE", std::move(layout))
|
state(*this, nullptr, "STATE", std::move(layout))
|
||||||
{
|
{
|
||||||
shifter.Init();
|
shifter.Init(48000.0f, 48);
|
||||||
}
|
}
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
void WebViewPluginAudioProcessor::prepareToPlay(double sampleRate, int samplesPerBlock)
|
void WebViewPluginAudioProcessor::prepareToPlay(double sampleRate, int samplesPerBlock)
|
||||||
{
|
{
|
||||||
const auto channels = std::max(getTotalNumInputChannels(), getTotalNumOutputChannels());
|
const auto channels = std::max(getTotalNumInputChannels(), getTotalNumOutputChannels());
|
||||||
shifter.Init();
|
shifter.Init((float)sampleRate, samplesPerBlock);
|
||||||
if (channels == 0)
|
if (channels == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -335,8 +338,17 @@ void WebViewPluginAudioProcessor::processBlock(juce::AudioBuffer<float>& buffer,
|
|||||||
for (const auto metadata : midi)
|
for (const auto metadata : midi)
|
||||||
{
|
{
|
||||||
const auto msg = metadata.getMessage();
|
const auto msg = metadata.getMessage();
|
||||||
if (msg.isNoteOn()) shifter.AddMidiNote(msg.getNoteNumber());
|
if (msg.isNoteOn()) {
|
||||||
else if (msg.isNoteOff()) shifter.RemoveMidiNote(msg.getNoteNumber());
|
shifter.AddMidiNote(msg.getNoteNumber());
|
||||||
|
new_midi = true;
|
||||||
|
//editor.webComponent.emitEventIfBrowserIsVisible("midNoteData", var{});
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (msg.isNoteOff()) {
|
||||||
|
shifter.RemoveMidiNote(msg.getNoteNumber());
|
||||||
|
new_midi = true;
|
||||||
|
//editor.webComponent.emitEventIfBrowserIsVisible("midNoteData", var{});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -344,12 +356,11 @@ void WebViewPluginAudioProcessor::processBlock(juce::AudioBuffer<float>& buffer,
|
|||||||
//DBG(shifter.out_midi[MAX_VOICES]);
|
//DBG(shifter.out_midi[MAX_VOICES]);
|
||||||
//push midi note
|
//push midi note
|
||||||
//spectralBars.push(shifter.out_midi[MAX_VOICES]);
|
//spectralBars.push(shifter.out_midi[MAX_VOICES]);
|
||||||
const SpinLock::ScopedTryLockType lock(spectrumDataLock);
|
const SpinLock::ScopedTryLockType lock(midiLock);
|
||||||
|
|
||||||
if (!lock.isLocked())
|
if (!lock.isLocked())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
spectralBars.compute({ spectrumData.data(), spectrumData.size() });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -446,9 +457,9 @@ public:
|
|||||||
{
|
{
|
||||||
static constexpr size_t numFramesBuffered = 5;
|
static constexpr size_t numFramesBuffered = 5;
|
||||||
|
|
||||||
SpinLock::ScopedLockType lock{ processorRef.spectrumDataLock };
|
SpinLock::ScopedLockType lock{ processorRef.midiLock };
|
||||||
|
|
||||||
Array<var> frame;
|
/*Array<var> frame;
|
||||||
|
|
||||||
for (size_t i = 1; i < processorRef.spectrumData.size(); ++i)
|
for (size_t i = 1; i < processorRef.spectrumData.size(); ++i)
|
||||||
frame.add(processorRef.spectrumData[i]);
|
frame.add(processorRef.spectrumData[i]);
|
||||||
@ -458,14 +469,18 @@ public:
|
|||||||
spectrumDataFrames.push_back(std::move(frame));
|
spectrumDataFrames.push_back(std::move(frame));
|
||||||
|
|
||||||
while (spectrumDataFrames.size() > numFramesBuffered)
|
while (spectrumDataFrames.size() > numFramesBuffered)
|
||||||
spectrumDataFrames.pop_front();
|
spectrumDataFrames.pop_front();*/
|
||||||
|
|
||||||
static int64 callbackCounter = 0;
|
static int64 callbackCounter = 0;
|
||||||
|
|
||||||
/*if ( spectrumDataFrames.size() == numFramesBuffered
|
/*if ( spectrumDataFrames.size() == numFramesBuffered
|
||||||
&& callbackCounter++ % (int64) numFramesBuffered)
|
&& callbackCounter++ % (int64) numFramesBuffered)
|
||||||
{*/
|
{*/
|
||||||
webComponent.emitEventIfBrowserIsVisible("spectrumData", var{});
|
if (processorRef.new_midi) {
|
||||||
|
processorRef.new_midi = false;
|
||||||
|
webComponent.emitEventIfBrowserIsVisible("midNoteData", var{});
|
||||||
|
}
|
||||||
|
|
||||||
//}
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -595,16 +610,22 @@ std::optional<WebBrowserComponent::Resource> WebViewPluginAudioProcessorEditor::
|
|||||||
return WebBrowserComponent::Resource{ streamToVector(stream), String { "text/html" } };
|
return WebBrowserComponent::Resource{ streamToVector(stream), String { "text/html" } };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (urlToRetrive == "spectrumData.json")
|
if (urlToRetrive == "midNoteData.json")
|
||||||
{
|
{
|
||||||
Array<var> frames;
|
juce::Array<var> notes;
|
||||||
|
int voice_num = 0;
|
||||||
for (const auto& frame : spectrumDataFrames)
|
for (auto& voice : processorRef.shifter.voices) {
|
||||||
frames.add(frame);
|
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());
|
DynamicObject::Ptr d(new DynamicObject());
|
||||||
d->setProperty("timeResolutionMs", getTimerInterval());
|
d->setProperty("notes", notes);
|
||||||
d->setProperty("frames", std::move(frames));
|
|
||||||
|
|
||||||
const auto s = JSON::toString(d.get());
|
const auto s = JSON::toString(d.get());
|
||||||
MemoryInputStream stream{ s.getCharPointer(), s.getNumBytesAsUTF8(), false };
|
MemoryInputStream stream{ s.getCharPointer(), s.getNumBytesAsUTF8(), false };
|
||||||
@ -652,7 +673,7 @@ WebViewPluginAudioProcessorEditor::WebViewPluginAudioProcessorEditor(WebViewPlug
|
|||||||
|
|
||||||
setSize(500, 500);
|
setSize(500, 500);
|
||||||
|
|
||||||
startTimerHz(20);
|
startTimerHz(60);
|
||||||
}
|
}
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
@ -675,5 +696,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool hasEditor() const override { return true; }
|
bool hasEditor() const override { return true; }
|
||||||
AudioProcessorEditor* createEditor() override { return new WebViewPluginAudioProcessorEditor(*this); }
|
AudioProcessorEditor* createEditor() override {
|
||||||
|
return new WebViewPluginAudioProcessorEditor(*this);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -18,18 +18,23 @@ static inline float mtof(float m)
|
|||||||
|
|
||||||
|
|
||||||
void ShifterVoice::Init(float sample_rate) {
|
void ShifterVoice::Init(float sample_rate) {
|
||||||
portamento_.Init(sample_rate, 0.05f); //default portamento time
|
sample_rate_ = sample_rate;
|
||||||
amplitude_envelope_.Init(sample_rate);
|
portamento_.Init(sample_rate_, 0.05f); //default portamento time
|
||||||
|
amplitude_envelope_.Init(sample_rate_);
|
||||||
amplitude_envelope_.SetAttackTime(0.2f);
|
amplitude_envelope_.SetAttackTime(0.2f);
|
||||||
amplitude_envelope_.SetDecayTime(0.1f);
|
amplitude_envelope_.SetDecayTime(0.1f);
|
||||||
amplitude_envelope_.SetReleaseTime(.1f);
|
amplitude_envelope_.SetReleaseTime(.05f);
|
||||||
onoff_ = false;
|
onoff_ = false;
|
||||||
overflow_ = false;
|
overflow_ = false;
|
||||||
current_midi = 60;
|
current_midi = 60;
|
||||||
current_period_ = 48000.0f / mtof((float)current_midi);
|
current_period_ = sample_rate_ / mtof((float)current_midi);
|
||||||
current_amplitude = 0.0f;
|
current_amplitude = 0.0f;
|
||||||
period_counter = 0.0f;
|
period_counter = 0.0f;
|
||||||
panning = 0.5f;
|
panning = 0.5f;
|
||||||
|
//on reset, make sure envelope is not running
|
||||||
|
while (amplitude_envelope_.IsRunning()) {
|
||||||
|
amplitude_envelope_.Process(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -49,7 +54,7 @@ void ShifterVoice::Release() {
|
|||||||
|
|
||||||
void ShifterVoice::Process() {
|
void ShifterVoice::Process() {
|
||||||
current_amplitude = amplitude_envelope_.Process(onoff_);
|
current_amplitude = amplitude_envelope_.Process(onoff_);
|
||||||
current_period_ = 48000.0f / mtof(portamento_.Process((float)current_midi));
|
current_period_ = sample_rate_ / mtof(portamento_.Process((float)current_midi));
|
||||||
period_counter++;
|
period_counter++;
|
||||||
overflow_ = period_counter >= current_period_;
|
overflow_ = period_counter >= current_period_;
|
||||||
if (overflow_) {
|
if (overflow_) {
|
||||||
|
|||||||
@ -32,12 +32,14 @@ public:
|
|||||||
void SetAdsrTimes(float attack, float decay, float release);
|
void SetAdsrTimes(float attack, float decay, float release);
|
||||||
float GetPanning(int channel) const;
|
float GetPanning(int channel) const;
|
||||||
int GetMidiNote() const { return current_midi; }
|
int GetMidiNote() const { return current_midi; }
|
||||||
|
bool onoff_;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Port portamento_;
|
Port portamento_;
|
||||||
Adsr amplitude_envelope_;
|
Adsr amplitude_envelope_;
|
||||||
bool onoff_;
|
|
||||||
bool overflow_;
|
bool overflow_;
|
||||||
|
float sample_rate_;
|
||||||
int current_midi;
|
int current_midi;
|
||||||
float current_period_;
|
float current_period_;
|
||||||
float current_amplitude;
|
float current_amplitude;
|
||||||
|
|||||||
Reference in New Issue
Block a user