diff --git a/Assets/web/src/App.js b/Assets/web/src/App.js
index fea956d..8de0d42 100644
--- a/Assets/web/src/App.js
+++ b/Assets/web/src/App.js
@@ -27,21 +27,22 @@ import "@fontsource/roboto/500.css";
import "@fontsource/roboto/700.css";
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 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 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";
@@ -122,262 +123,323 @@ function JuceSlider({ identifier, title }) {
);
}
-function JuceCheckbox({ identifier }) {
- JuceCheckbox.propTypes = {
- identifier: PropTypes.string,
- };
+// function JuceCheckbox({ identifier }) {
+// JuceCheckbox.propTypes = {
+// identifier: PropTypes.string,
+// };
- const checkboxState = Juce.getToggleState(identifier);
+// const checkboxState = Juce.getToggleState(identifier);
- const [value, setValue] = useState(checkboxState.getValue());
- const [properties, setProperties] = useState(checkboxState.properties);
+// const [value, setValue] = useState(checkboxState.getValue());
+// const [properties, setProperties] = useState(checkboxState.properties);
- const handleChange = (event) => {
- checkboxState.setValue(event.target.checked);
- setValue(event.target.checked);
- };
+// 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)
- );
+// 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);
- };
- });
+// return function cleanup() {
+// checkboxState.valueChangedEvent.removeListener(valueListenerId);
+// checkboxState.propertiesChangedEvent.removeListener(propertiesListenerId);
+// };
+// });
- const cb = ;
+// const cb = ;
- return (
-
-
-
-
-
- );
-}
+// return (
+//
+//
+//
+//
+//
+// );
+// }
-function JuceComboBox({ identifier }) {
- JuceComboBox.propTypes = {
- identifier: PropTypes.string,
- };
+// function JuceComboBox({ identifier }) {
+// JuceComboBox.propTypes = {
+// identifier: PropTypes.string,
+// };
- const comboBoxState = Juce.getComboBoxState(identifier);
+// const comboBoxState = Juce.getComboBoxState(identifier);
- const [value, setValue] = useState(comboBoxState.getChoiceIndex());
- const [properties, setProperties] = useState(comboBoxState.properties);
+// const [value, setValue] = useState(comboBoxState.getChoiceIndex());
+// const [properties, setProperties] = useState(comboBoxState.properties);
- const handleChange = (event) => {
- comboBoxState.setChoiceIndex(event.target.value);
- setValue(event.target.value);
- };
+// 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);
- });
+// 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 function cleanup() {
+// comboBoxState.valueChangedEvent.removeListener(valueListenerId);
+// comboBoxState.propertiesChangedEvent.removeListener(propertiesListenerId);
+// };
+// });
- return (
-
-
- {properties.name}
-
-
-
- );
-}
+// return (
+//
+//
+// {properties.name}
+//
+//
+//
+// );
+// }
-const sayHello = Juce.getNativeFunction("sayHello");
+// const sayHello = Juce.getNativeFunction("sayHello");
-const SpectrumDataReceiver_eventId = "spectrumData";
+// const SpectrumDataReceiver_eventId = "spectrumData";
-function interpolate(a, b, s) {
- let result = new Array(a.length).fill(0);
+// 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 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) {
- const quotient = Math.floor(dividend / divisor);
- return dividend - divisor * quotient;
-}
+// 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;
+// 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.spectrumDataRegistrationId = window.__JUCE__.backend.addEventListener(
- SpectrumDataReceiver_eventId,
+ this.midNoteDataRegistrationId = window.__JUCE__.backend.addEventListener(
+ MidNoteDataReceiver_eventId,
() => {
- fetch(Juce.getBackendResourceAddress("spectrumData.json"))
+ fetch(Juce.getBackendResourceAddress("midNoteData.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;
+ self.notes = data.notes;
});
}
);
}
- 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
- );
+ getNotes() {
+ return this.notes;
}
unregister() {
- window.__JUCE__.backend.removeEventListener(
- this.spectrumDataRegistrationId
- );
+ window.__JUCE__.backend.removeEventListener(this.midNoteDataRegistrationId);
}
}
-function FreqBandInfo() {
- const canvasRef = useRef(null);
- let dataReceiver = null;
- let isActive = true;
+function MidiNoteInfo() {
+ const [notes, setNotes] = useState([]);
+ const dataReceiverRef = useRef(null);
+ 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);
+ useEffect(() => {
+ dataReceiverRef.current = new MidNoteDataReceiver();
+ isActiveRef.current = true;
- 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
- );
- }
+ function render() {
+ if (dataReceiverRef.current) {
+ setNotes(dataReceiverRef.current.getNotes());
+ }
+ if (isActiveRef.current) {
+ window.requestAnimationFrame(render);
}
}
- if (isActive) window.requestAnimationFrame(render);
- };
-
- useEffect(() => {
- dataReceiver = new SpectrumDataReceiver(10);
- isActive = true;
window.requestAnimationFrame(render);
return function cleanup() {
- isActive = false;
- dataReceiver.unregister();
+ isActiveRef.current = false;
+ if (dataReceiverRef.current) {
+ dataReceiverRef.current.unregister();
+ }
};
- });
-
- const canvasStyle = {
- marginLeft: "0",
- marginRight: "0",
- marginTop: "1em",
- display: "block",
- width: "94%",
- bottom: "0",
- position: "absolute",
- };
+ }, []);
return (
-
-
-
+
);
}
+// 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 (
+//
+//
+//
+// );
+// }
+
function App() {
const controlParameterIndexUpdater = new Juce.ControlParameterIndexUpdater(
controlParameterIndexAnnotation
@@ -387,33 +449,33 @@ function App() {
controlParameterIndexUpdater.handleMouseMove(event);
});
- const [open, setOpen] = useState(false);
- const [snackbarMessage, setMessage] = useState("No message received yet");
+ // const [open, setOpen] = useState(false);
+ // const [snackbarMessage, setMessage] = useState("No message received yet");
- const openSnackbar = () => {
- setOpen(true);
- };
+ // const openSnackbar = () => {
+ // setOpen(true);
+ // };
- const handleClose = (event, reason) => {
- if (reason === "clickaway") {
- return;
- }
+ // const handleClose = (event, reason) => {
+ // if (reason === "clickaway") {
+ // return;
+ // }
- setOpen(false);
- };
+ // setOpen(false);
+ // };
- const action = (
- <>
-
-
-
- >
- );
+ // const action = (
+ // <>
+ //
+ //
+ //
+ // >
+ // );
return (
@@ -422,7 +484,8 @@ function App() {
-
+
+ {/*
);
}
diff --git a/Assets/web/src/Components/PianoKeyboard.js b/Assets/web/src/Components/PianoKeyboard.js
new file mode 100644
index 0000000..81a67bf
--- /dev/null
+++ b/Assets/web/src/Components/PianoKeyboard.js
@@ -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 (
+
+ {/* White keys */}
+
+ {whiteKeys.map((k) => (
+
+
+ {k.noteName}
+ {k.held && (
+
+ {k.voice}
+
+ )}
+
+
+ ))}
+
+ {/* Black keys */}
+
+ {blackKeys.map((k) => (
+
+
+ {k.held && (
+
+ {k.voice}
+
+ )}
+
+
+ ))}
+
+
+ );
+}
diff --git a/Source/Shifter.cpp b/Source/Shifter.cpp
index 36a37ad..562bd0d 100644
--- a/Source/Shifter.cpp
+++ b/Source/Shifter.cpp
@@ -67,14 +67,17 @@ static inline bool float_equal(float one, float two) {
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;
helm.setframesize(1024);
helm.setoverlap(1);
for (int i = 0; i < MAX_VOICES; ++i)
{
- voices[i].Init(48000);
+ voices[i].Init(samplerate);
}
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
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);
//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_period = 48000.0f / mtof(out_midi);
+ out_period = sample_rate_ / mtof(out_midi);
}
void Shifter::SetRates() {}
@@ -275,8 +278,8 @@ void Shifter::GetSamples(float** output, const float* input, size_t size)
//add new samples if necessary
for (int out_p = 0; out_p < MAX_VOICES; ++out_p)
{
+ voices[out_p].Process();
if (!voices[out_p].IsActive()) continue;
- voices[out_p].Process();
if (voices[out_p].PeriodOverflow())
{
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) {
for (int i = 0; i < MAX_VOICES; ++i) {
- if (voices[i].IsActive() && voices[i].GetMidiNote() == note) {
- return;
+ if (voices[i].onoff_ && voices[i].GetMidiNote() == note) {
+ voices[i].Release();
}
}
for (int i = 0; i < MAX_VOICES; ++i) {
- if (!voices[i].IsActive()) {
+ if (!voices[i].onoff_) {
voices[i].Trigger(note);
return;
}
@@ -343,9 +346,8 @@ void Shifter::AddMidiNote(int note) {
void Shifter::RemoveMidiNote(int note) {
for (int i = 0; i < MAX_VOICES; ++i) {
- if (voices[i].IsActive() && voices[i].GetMidiNote() == note) {
+ if (voices[i].GetMidiNote() == note) {
voices[i].Release();
- return;
}
}
}
\ No newline at end of file
diff --git a/Source/Shifter.h b/Source/Shifter.h
index 4494ac5..9af3288 100644
--- a/Source/Shifter.h
+++ b/Source/Shifter.h
@@ -75,7 +75,7 @@ public:
class Shifter {
public:
- void Init();
+ void Init(float samplerate, int samplesPerBlock);
void Process(const float* const* in,
float** out,
size_t size);
@@ -91,6 +91,7 @@ public:
}
float out_midi = 40;
+ ShifterVoice voices[MAX_VOICES];
private:
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
- ShifterVoice voices[MAX_VOICES];
+
float out_period = 0; //C3
float in_period = 366.936;
float out_period_counter = 0;
float cos_lookup[8192];
+ float sample_rate_;
+ int blocksize;
};
#endif
\ No newline at end of file
diff --git a/Source/WebViewPluginDemo.h b/Source/WebViewPluginDemo.h
index 99ea5bd..c941a22 100644
--- a/Source/WebViewPluginDemo.h
+++ b/Source/WebViewPluginDemo.h
@@ -145,26 +145,26 @@ private:
int64 writeIx = 0;
};
-class SpectralBars
-{
-public:
- //template
- void push(int data)
- {
- testQueue.push(data);
- }
-
- void compute(Span output) {
- int index = 0;
- for (auto it = output.begin(); it != output.end(); ++it) {
- *it = testQueue.get(index++);
- }
- }
-
-
-private:
- circ_queue testQueue;
-};
+//class SpectralBars
+//{
+//public:
+// //template
+// void push(int data)
+// {
+// testQueue.push(data);
+// }
+//
+// void compute(Span output) {
+// int index = 0;
+// for (auto it = output.begin(); it != output.end(); ++it) {
+// *it = testQueue.get(index++);
+// }
+// }
+//
+//
+//private:
+// circ_queue testQueue;
+//};
//==============================================================================
class WebViewPluginAudioProcessor : public AudioProcessor
@@ -200,6 +200,7 @@ public:
//==============================================================================
void getStateInformation(MemoryBlock& destData) override;
void setStateInformation(const void* data, int sizeInBytes) override;
+ bool new_midi = false;
struct Parameters
{
@@ -261,18 +262,20 @@ public:
Parameters parameters;
AudioProcessorValueTreeState state;
+ SpinLock midiLock;
- std::vector spectrumData = [] { return std::vector(256, 0.0f); }();
+ /*std::vector spectrumData = [] { return std::vector(256, 0.0f); }();
SpinLock spectrumDataLock;
- SpectralBars spectralBars;
+ SpectralBars spectralBars;*/
dsp::LadderFilter filter;
+ Shifter shifter;
private:
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(WebViewPluginAudioProcessor)
- Shifter shifter;
+
};
//==============================================================================
@@ -288,14 +291,14 @@ WebViewPluginAudioProcessor::WebViewPluginAudioProcessor(AudioProcessorValueTree
parameters(layout),
state(*this, nullptr, "STATE", std::move(layout))
{
- shifter.Init();
+ shifter.Init(48000.0f, 48);
}
//==============================================================================
void WebViewPluginAudioProcessor::prepareToPlay(double sampleRate, int samplesPerBlock)
{
const auto channels = std::max(getTotalNumInputChannels(), getTotalNumOutputChannels());
- shifter.Init();
+ shifter.Init((float)sampleRate, samplesPerBlock);
if (channels == 0)
return;
@@ -335,8 +338,17 @@ void WebViewPluginAudioProcessor::processBlock(juce::AudioBuffer& buffer,
for (const auto metadata : midi)
{
const auto msg = metadata.getMessage();
- if (msg.isNoteOn()) shifter.AddMidiNote(msg.getNoteNumber());
- else if (msg.isNoteOff()) shifter.RemoveMidiNote(msg.getNoteNumber());
+ if (msg.isNoteOn()) {
+ 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& buffer,
//DBG(shifter.out_midi[MAX_VOICES]);
//push midi note
//spectralBars.push(shifter.out_midi[MAX_VOICES]);
- const SpinLock::ScopedTryLockType lock(spectrumDataLock);
+ const SpinLock::ScopedTryLockType lock(midiLock);
if (!lock.isLocked())
return;
- spectralBars.compute({ spectrumData.data(), spectrumData.size() });
}
@@ -446,9 +457,9 @@ public:
{
static constexpr size_t numFramesBuffered = 5;
- SpinLock::ScopedLockType lock{ processorRef.spectrumDataLock };
+ SpinLock::ScopedLockType lock{ processorRef.midiLock };
- Array frame;
+ /*Array frame;
for (size_t i = 1; i < processorRef.spectrumData.size(); ++i)
frame.add(processorRef.spectrumData[i]);
@@ -458,14 +469,18 @@ public:
spectrumDataFrames.push_back(std::move(frame));
while (spectrumDataFrames.size() > numFramesBuffered)
- spectrumDataFrames.pop_front();
+ spectrumDataFrames.pop_front();*/
static int64 callbackCounter = 0;
/*if ( spectrumDataFrames.size() == 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 WebViewPluginAudioProcessorEditor::
return WebBrowserComponent::Resource{ streamToVector(stream), String { "text/html" } };
}
- if (urlToRetrive == "spectrumData.json")
+ if (urlToRetrive == "midNoteData.json")
{
- Array frames;
-
- for (const auto& frame : spectrumDataFrames)
- frames.add(frame);
+ juce::Array 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("timeResolutionMs", getTimerInterval());
- d->setProperty("frames", std::move(frames));
+ d->setProperty("notes", notes);
const auto s = JSON::toString(d.get());
MemoryInputStream stream{ s.getCharPointer(), s.getNumBytesAsUTF8(), false };
@@ -652,7 +673,7 @@ WebViewPluginAudioProcessorEditor::WebViewPluginAudioProcessorEditor(WebViewPlug
setSize(500, 500);
- startTimerHz(20);
+ startTimerHz(60);
}
//==============================================================================
@@ -675,5 +696,7 @@ public:
}
bool hasEditor() const override { return true; }
- AudioProcessorEditor* createEditor() override { return new WebViewPluginAudioProcessorEditor(*this); }
+ AudioProcessorEditor* createEditor() override {
+ return new WebViewPluginAudioProcessorEditor(*this);
+ }
};
diff --git a/Source/shifter_voice.cpp b/Source/shifter_voice.cpp
index 4250c56..dd50a3a 100644
--- a/Source/shifter_voice.cpp
+++ b/Source/shifter_voice.cpp
@@ -18,18 +18,23 @@ static inline float mtof(float m)
void ShifterVoice::Init(float sample_rate) {
- portamento_.Init(sample_rate, 0.05f); //default portamento time
- amplitude_envelope_.Init(sample_rate);
+ sample_rate_ = sample_rate;
+ portamento_.Init(sample_rate_, 0.05f); //default portamento time
+ amplitude_envelope_.Init(sample_rate_);
amplitude_envelope_.SetAttackTime(0.2f);
amplitude_envelope_.SetDecayTime(0.1f);
- amplitude_envelope_.SetReleaseTime(.1f);
+ amplitude_envelope_.SetReleaseTime(.05f);
onoff_ = false;
overflow_ = false;
current_midi = 60;
- current_period_ = 48000.0f / mtof((float)current_midi);
+ current_period_ = sample_rate_ / mtof((float)current_midi);
current_amplitude = 0.0f;
period_counter = 0.0f;
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() {
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++;
overflow_ = period_counter >= current_period_;
if (overflow_) {
diff --git a/Source/shifter_voice.h b/Source/shifter_voice.h
index 029b945..373fa46 100644
--- a/Source/shifter_voice.h
+++ b/Source/shifter_voice.h
@@ -32,12 +32,14 @@ public:
void SetAdsrTimes(float attack, float decay, float release);
float GetPanning(int channel) const;
int GetMidiNote() const { return current_midi; }
+ bool onoff_;
private:
Port portamento_;
Adsr amplitude_envelope_;
- bool onoff_;
+
bool overflow_;
+ float sample_rate_;
int current_midi;
float current_period_;
float current_amplitude;