This commit is contained in:
michalcourson
2025-10-04 10:09:14 -04:00
commit 720a013ff3
93 changed files with 36220 additions and 0 deletions

232
Source/DemoUtilities.h Normal file
View File

@ -0,0 +1,232 @@
/*
==============================================================================
This file is part of the JUCE framework examples.
Copyright (c) Raw Material Software Limited
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
to use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
==============================================================================
*/
#ifndef PIP_DEMO_UTILITIES_INCLUDED
#define PIP_DEMO_UTILITIES_INCLUDED 1
#include <JuceHeader.h>
//==============================================================================
/*
This file contains a bunch of miscellaneous utilities that are
used by the various demos.
*/
//==============================================================================
inline Colour getRandomColour (float brightness) noexcept
{
return Colour::fromHSV (Random::getSystemRandom().nextFloat(), 0.5f, brightness, 1.0f);
}
inline Colour getRandomBrightColour() noexcept { return getRandomColour (0.8f); }
inline Colour getRandomDarkColour() noexcept { return getRandomColour (0.3f); }
inline Colour getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour uiColour, Colour fallback = Colour (0xff4d4d4d)) noexcept
{
if (auto* v4 = dynamic_cast<LookAndFeel_V4*> (&LookAndFeel::getDefaultLookAndFeel()))
return v4->getCurrentColourScheme().getUIColour (uiColour);
return fallback;
}
enum class AssertAssetExists
{
no,
yes
};
inline std::unique_ptr<InputStream> createAssetInputStream (const char* resourcePath,
[[maybe_unused]] AssertAssetExists assertExists = AssertAssetExists::yes)
{
auto assetsDir = File::getCurrentWorkingDirectory().getChildFile("..\\..\\Assets");
auto resourceFile = assetsDir.getChildFile (resourcePath);
if (! resourceFile.existsAsFile())
{
jassert (assertExists == AssertAssetExists::no);
return {};
}
return resourceFile.createInputStream();
}
inline Image getImageFromAssets (const char* assetName)
{
auto hashCode = (String (assetName) + "@juce_demo_assets").hashCode64();
auto img = ImageCache::getFromHashCode (hashCode);
if (img.isNull())
{
std::unique_ptr<InputStream> juceIconStream (createAssetInputStream (assetName));
if (juceIconStream == nullptr)
return {};
img = ImageFileFormat::loadFrom (*juceIconStream);
ImageCache::addImageToCache (img, hashCode);
}
return img;
}
inline String loadEntireAssetIntoString (const char* assetName)
{
std::unique_ptr<InputStream> input (createAssetInputStream (assetName));
if (input == nullptr)
return {};
return input->readString();
}
//==============================================================================
inline Path getJUCELogoPath()
{
return Drawable::parseSVGPath (
"M72.87 84.28A42.36 42.36 0 0130.4 42.14a42.48 42.48 0 0184.95 0 42.36 42.36 0 01-42.48 42.14zm0-78.67A36.74 36.74 0 0036 42.14a36.88 36.88 0 0073.75 0A36.75 36.75 0 0072.87 5.61z"
"M77.62 49.59a177.77 177.77 0 008.74 18.93A4.38 4.38 0 0092.69 70a34.5 34.5 0 008.84-9 4.3 4.3 0 00-2.38-6.49A176.73 176.73 0 0180 47.32a1.78 1.78 0 00-2.38 2.27zM81.05 44.27a169.68 169.68 0 0020.13 7.41 4.39 4.39 0 005.52-3.41 34.42 34.42 0 00.55-6.13 33.81 33.81 0 00-.67-6.72 4.37 4.37 0 00-6.31-3A192.32 192.32 0 0181.1 41a1.76 1.76 0 00-.05 3.27zM74.47 50.44a1.78 1.78 0 00-3.29 0 165.54 165.54 0 00-7.46 19.89 4.33 4.33 0 003.47 5.48 35.49 35.49 0 005.68.46 34.44 34.44 0 007.13-.79 4.32 4.32 0 003-6.25 187.83 187.83 0 01-8.53-18.79zM71.59 34.12a1.78 1.78 0 003.29.05 163.9 163.9 0 007.52-20.11A4.34 4.34 0 0079 8.59a35.15 35.15 0 00-13.06.17 4.32 4.32 0 00-3 6.26 188.41 188.41 0 018.65 19.1zM46.32 30.3a176.2 176.2 0 0120 7.48 1.78 1.78 0 002.37-2.28 180.72 180.72 0 00-9.13-19.84 4.38 4.38 0 00-6.33-1.47 34.27 34.27 0 00-9.32 9.65 4.31 4.31 0 002.41 6.46zM68.17 49.18a1.77 1.77 0 00-2.29-2.34 181.71 181.71 0 00-19.51 8.82A4.3 4.3 0 0044.91 62a34.36 34.36 0 009.42 8.88 4.36 4.36 0 006.5-2.38 175.11 175.11 0 017.34-19.32zM77.79 35.59a1.78 1.78 0 002.3 2.35 182.51 182.51 0 0019.6-8.88 4.3 4.3 0 001.5-6.25 34.4 34.4 0 00-9.41-9.14A4.36 4.36 0 0085.24 16a174.51 174.51 0 01-7.45 19.59zM64.69 40.6a167.72 167.72 0 00-20.22-7.44A4.36 4.36 0 0039 36.6a33.68 33.68 0 00-.45 5.54 34 34 0 00.81 7.4 4.36 4.36 0 006.28 2.84 189.19 189.19 0 0119-8.52 1.76 1.76 0 00.05-3.26zM20 129.315c0 5-2.72 8.16-7.11 8.16-2.37 0-4.17-1-6.2-3.56l-.69-.78-6 5 .57.76c3.25 4.36 7.16 6.39 12.31 6.39 9 0 15.34-6.57 15.34-16v-28.1H20zM61.69 126.505c0 6.66-3.76 11-9.57 11-5.81 0-9.56-4.31-9.56-11v-25.32h-8.23v25.69c0 10.66 7.4 18.4 17.6 18.4 10 0 17.61-7.72 18-18.4v-25.69h-8.24zM106.83 134.095c-3.58 2.43-6.18 3.38-9.25 3.38a14.53 14.53 0 010-29c3.24 0 5.66.88 9.25 3.38l.76.53 4.78-6-.75-.62a22.18 22.18 0 00-14.22-5.1 22.33 22.33 0 100 44.65 21.53 21.53 0 0014.39-5.08l.81-.64-5-6zM145.75 137.285h-19.06v-10.72h18.3v-7.61h-18.3v-10.16h19.06v-7.61h-27.28v43.53h27.28z"
"M68.015 83.917c-7.723-.902-15.472-4.123-21.566-8.966-8.475-6.736-14.172-16.823-15.574-27.575C29.303 35.31 33.538 22.7 42.21 13.631 49.154 6.368 58.07 1.902 68.042.695c2.15-.26 7.524-.26 9.675 0 12.488 1.512 23.464 8.25 30.437 18.686 8.332 12.471 9.318 28.123 2.605 41.368-2.28 4.5-4.337 7.359-7.85 10.909A42.273 42.273 0 0177.613 83.92c-2.027.227-7.644.225-9.598-.003zm7.823-5.596c8.435-.415 17.446-4.678 23.683-11.205 5.976-6.254 9.35-13.723 10.181-22.537.632-6.705-1.346-14.948-5.065-21.108C98.88 13.935 89.397 7.602 78.34 5.906c-2.541-.39-8.398-.386-10.96.006C53.54 8.034 42.185 17.542 37.81 30.67c-2.807 8.426-2.421 17.267 1.11 25.444 4.877 11.297 14.959 19.41 26.977 21.709 2.136.408 6.1.755 7.377.645.325-.028 1.48-.094 2.564-.147z"
);
}
//==============================================================================
#if JUCE_MODULE_AVAILABLE_juce_gui_extra
inline CodeEditorComponent::ColourScheme getDarkCodeEditorColourScheme()
{
struct Type
{
const char* name;
juce::uint32 colour;
};
const Type types[] =
{
{ "Error", 0xffe60000 },
{ "Comment", 0xff72d20c },
{ "Keyword", 0xffee6f6f },
{ "Operator", 0xffc4eb19 },
{ "Identifier", 0xffcfcfcf },
{ "Integer", 0xff42c8c4 },
{ "Float", 0xff885500 },
{ "String", 0xffbc45dd },
{ "Bracket", 0xff058202 },
{ "Punctuation", 0xffcfbeff },
{ "Preprocessor Text", 0xfff8f631 }
};
CodeEditorComponent::ColourScheme cs;
for (auto& t : types)
cs.set (t.name, Colour (t.colour));
return cs;
}
inline CodeEditorComponent::ColourScheme getLightCodeEditorColourScheme()
{
struct Type
{
const char* name;
juce::uint32 colour;
};
const Type types[] =
{
{ "Error", 0xffcc0000 },
{ "Comment", 0xff00aa00 },
{ "Keyword", 0xff0000cc },
{ "Operator", 0xff225500 },
{ "Identifier", 0xff000000 },
{ "Integer", 0xff880000 },
{ "Float", 0xff885500 },
{ "String", 0xff990099 },
{ "Bracket", 0xff000055 },
{ "Punctuation", 0xff004400 },
{ "Preprocessor Text", 0xff660000 }
};
CodeEditorComponent::ColourScheme cs;
for (auto& t : types)
cs.set (t.name, Colour (t.colour));
return cs;
}
#endif
//==============================================================================
// This is basically a sawtooth wave generator - maps a value that bounces between
// 0.0 and 1.0 at a random speed
struct BouncingNumber
{
virtual ~BouncingNumber() = default;
float getValue() const
{
double v = fmod (phase + speed * Time::getMillisecondCounterHiRes(), 2.0);
return (float) (v >= 1.0 ? (2.0 - v) : v);
}
protected:
double speed = 0.0004 + 0.0007 * Random::getSystemRandom().nextDouble(),
phase = Random::getSystemRandom().nextDouble();
};
struct SlowerBouncingNumber final : public BouncingNumber
{
SlowerBouncingNumber()
{
speed *= 0.3;
}
};
inline std::unique_ptr<InputSource> makeInputSource (const URL& url)
{
if (const auto doc = AndroidDocument::fromDocument (url))
return std::make_unique<AndroidDocumentInputSource> (doc);
#if ! JUCE_IOS
if (url.isLocalFile())
return std::make_unique<FileInputSource> (url.getLocalFile());
#endif
return std::make_unique<URLInputSource> (url);
}
inline std::unique_ptr<OutputStream> makeOutputStream (const URL& url)
{
if (const auto doc = AndroidDocument::fromDocument (url))
return doc.createOutputStream();
#if ! JUCE_IOS
if (url.isLocalFile())
return url.getLocalFile().createOutputStream();
#endif
return url.createOutputStream();
}
#endif // PIP_DEMO_UTILITIES_INCLUDED

271
Source/Helmholtz.cpp Normal file
View File

@ -0,0 +1,271 @@
/*
Class Helmholtz implements a period-length detector using Philip McLeod's
Specially Normalized AutoCorrelation function (SNAC).
Licensed under three-clause BSD license.
Katja Vetter, Feb 2012.
*/
#include <stdlib.h>
#include <math.h>
#include "Helmholtz.h"
Helmholtz::Helmholtz(int framearg, int overlaparg, t_float biasarg)
{
setframesize(framearg);
setoverlap(overlaparg);
if (biasarg)setbias(biasarg);
else biasfactor = DEFBIAS;
timeindex = 0;
periodindex = 0;
periodlength = 0.;
fidelity = 0.;
minrms = DEFMINRMS;
}
Helmholtz::~Helmholtz()
{
}
/*********************************************************************************/
/******************************** public *****************************************/
/*********************************************************************************/
void Helmholtz::iosamples(const t_float* in, t_float* out, int size)
{
int mask = framesize - 1;
int outindex = 0;
// call analysis function when it is time
if (!(timeindex & (framesize / overlap - 1))) analyzeframe();
while (size--)
{
inputbuf[timeindex++] = *in++;
//out[outindex++] = processbuf[timeindex++];
timeindex &= mask;
}
}
void Helmholtz::setframesize(int frame)
{
if (!((frame == 128) || (frame == 256) || (frame == 512) || (frame == 1024) || (frame == 2048)))
frame = DEFFRAMESIZE;
framesize = frame;
timeindex = 0;
}
void Helmholtz::setoverlap(int lap)
{
if (!((lap == 1) || (lap == 2) || (lap == 4) || (lap == 8)))
lap = DEFOVERLAP;
overlap = lap;
}
void Helmholtz::setbias(t_float bias)
{
if (bias > 1.) bias = 1.;
if (bias < 0.) bias = 0.;
biasfactor = bias;
}
void Helmholtz::setminRMS(t_float rms)
{
if (rms > 1.) rms = 1.;
if (rms < 0.) rms = 0.;
minrms = rms;
}
t_float Helmholtz::getperiod() const
{
return(periodlength);
}
t_float Helmholtz::getfidelity() const
{
return(fidelity);
}
/*********************************************************************************/
/***************************** private procedures ********************************/
/*********************************************************************************/
// main analysis function
void Helmholtz::analyzeframe()
{
int n, tindex = timeindex;
int mask = framesize - 1;
int peak;
t_float norm = 1. / sqrt(t_float(framesize * 2));
// copy input to processing buffer
for (n = 0; n < framesize; n++) processbuf[n] = inputbuf[tindex++ & mask] * norm;
// copy for normalization function
for (n = 0; n < framesize; n++) inputbuf2[n] = inputbuf[tindex++ & mask];
// zeropadding
for (n = framesize; n < (framesize << 1); n++) processbuf[n] = 0.;
// call analysis procedures
autocorrelation();
normalize();
pickpeak();
periodandfidelity();
}
void Helmholtz::autocorrelation()
{
int n;
int fftsize = framesize * 2;
REALFFT(fftsize, processbuf);
// compute power spectrum
processbuf[0] *= processbuf[0]; // DC
processbuf[framesize] *= processbuf[framesize]; // Nyquist
for (n = 1; n < framesize; n++)
{
processbuf[n] = processbuf[n] * processbuf[n]
+ processbuf[fftsize - n] * processbuf[fftsize - n]; // imag coefficients appear reversed
processbuf[fftsize - n] = 0.;
}
REALIFFT(fftsize, processbuf);
}
void Helmholtz::normalize()
{
int n, mask = framesize - 1;
int seek = framesize * SEEK;
t_float signal1, signal2;
// minimum RMS implemented as minimum autocorrelation at index 0
// effectively this means possible white noise addition
t_float rms = minrms / sqrt(1. / (t_float)framesize);
t_float minrzero = rms * rms;
t_float rzero = processbuf[0];
if (rzero < minrzero) rzero = minrzero;
double normintegral = rzero * 2.;
// normalize biased autocorrelation function
processbuf[0] = 1.;
for (n = 1; n < seek; n++)
{
signal1 = inputbuf2[n - 1];
signal2 = inputbuf2[framesize - n];
normintegral -= (double)(signal1 * signal1 + signal2 * signal2);
processbuf[n] /= (t_float)normintegral * 0.5;
}
// flush instable function tail
for (n = seek; n < framesize; n++) processbuf[n] = 0.;
}
// select the peak which most probably represents period length
void Helmholtz::pickpeak()
{
int n, peakindex = 0;
int seek = framesize * SEEK;
t_float maxvalue = 0.;
t_float previous[2];
t_float bias = biasfactor / (t_float)framesize; // user-controlled bias
t_float realpeak;
// skip main lobe
for (n = 1; n < seek; n++)
{
if (processbuf[n] < 0.) break;
}
// find interpolated / biased maximum in specially normalized autocorrelation function
// interpolation finds the 'real maximum'
// biasing favours the first candidate
for (; n < seek - 1; n++)
{
if (processbuf[n] > processbuf[n - 1])
{
if (processbuf[n] > processbuf[n + 1]) // we have a local peak
{
realpeak = interpolate3max(processbuf, n);
if ((realpeak * (1. - n * bias)) > maxvalue)
{
maxvalue = realpeak;
peakindex = n;
}
}
}
}
periodindex = peakindex;
}
void Helmholtz::periodandfidelity()
{
if (periodindex)
{
periodlength = periodindex + interpolate3phase(processbuf, periodindex);
fidelity = interpolate3max(processbuf, periodindex);
}
}
/*********************************************************************************/
/***************************** private functions *********************************/
/*********************************************************************************/
inline t_float Helmholtz::interpolate3max(t_float* buf, int peakindex)
{
t_float realpeak;
t_float a = buf[peakindex - 1];
t_float b = buf[peakindex];
t_float c = buf[peakindex + 1];
realpeak = b + 0.5 * (0.5 * ((c - a) * (c - a)))
/ (2 * b - a - c);
return(realpeak);
}
inline t_float Helmholtz::interpolate3phase(t_float* buf, int peakindex)
{
t_float fraction;
t_float a = buf[peakindex - 1];
t_float b = buf[peakindex];
t_float c = buf[peakindex + 1];
fraction = (0.5 * (c - a)) / (2. * b - a - c);
return(fraction);
}

109
Source/Helmholtz.h Normal file
View File

@ -0,0 +1,109 @@
#ifndef Helmholtz_H
#define Helmholtz_H
/***********************************************************************
Class Helmholtz implements a period-length detector using Philip McLeod's
Specially Normalized AutoCorrelation function (SNAC).
Function iosamples() takes a pointer to a buffer with n signal input samples
and a pointer to a buffer where n output samples are stored,
representing the SNAC function.
Via function setframesize(), analysis frame size can be set to
128, 256, 512, 1024 or 2048 samples. Default is 1024.
With setoverlap(), analysis frames can be set to overlap each other
with factor 1, 2, 4 or 8. Default is 1.
Function setbias() sets a bias which favours small lags over large lags in
the period detection, thereby avoiding low-octave jumps. Default is 0.2
Function setminRMS() is used as a sensitivity setting. Default is RMS 0.003.
With function getperiod(), the last detected period length is returned
as number of samples with a possible fraction (floating point format).
Function getfidelity() returns a value between 0. and 1. indicating
to which extent the input signal is periodic. A fidelity of ~0.95 can
be considered to indicate a periodic signal.
Class Helmholtz needs mayer_realfft() and mayer_realifft() or similar
fft functions. Note that Ron Mayer's functions for real fft have a
peculiar organisation of imaginary coefficients (reversed order, sign flipped).
Class Helmholtz uses t_float for float or double. Depending on the context
where the class is used, you may need to define t_float. If used with
PD or MaxMsp, it is already defined.
***********************************************************************
Licensed under three-clause BSD license.
Katja Vetter, Feb 2012.
***********************************************************************/
/* This section includes the Pure Data API header. If you build Helmholtz
against another DSP framework, you need to define t_float, and you need to
include Ron Mayer's fft or similar functionality. */
#include "mayer_fft.h"
#define REALFFT mayer_realfft
#define REALIFFT mayer_realifft
/***********************************************************************/
#define DEFFRAMESIZE 1024 // default analysis framesize
#define DEFOVERLAP 1 // default overlap
#define DEFBIAS 0.2 // default bias
#define DEFMINRMS 0.003 // default minimum RMS
#define SEEK 0.85 // seek-length as ratio of framesize
#define t_float float
class Helmholtz
{
public:
Helmholtz(int periodarg = DEFFRAMESIZE, int overlaparg = DEFOVERLAP, t_float biasarg = DEFBIAS);
~Helmholtz();
void iosamples(const t_float* in, t_float* out, int size);
void setframesize(int frame);
void setoverlap(int lap);
void setbias(t_float bias);
void setminRMS(t_float rms);
t_float getperiod() const;
t_float getfidelity() const;
private:
// procedures
void analyzeframe();
void autocorrelation();
void normalize();
void pickpeak();
void periodandfidelity();
// functions
t_float interpolate3max(t_float* buf, int peakindex);
t_float interpolate3phase(t_float* buf, int peakindex);
// buffers
t_float inputbuf[2048];
t_float inputbuf2[2048];
t_float processbuf[4096];
// state variables
int timeindex;
int framesize;
int overlap;
int periodindex;
t_float periodlength;
t_float fidelity;
t_float biasfactor;
t_float minrms;
};
#endif // #ifndef Helmholtz_H

16
Source/Main.cpp Normal file
View File

@ -0,0 +1,16 @@
/*
==============================================================================
This file was auto-generated and contains the startup code for a PIP.
==============================================================================
*/
#include <JuceHeader.h>
#include "WebViewPluginDemo.h"
//==============================================================================
juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter()
{
return new WebViewPluginAudioProcessorWrapper();
}

285
Source/Shifter.cpp Normal file
View File

@ -0,0 +1,285 @@
#include "Shifter.h"
#include <math.h>
#include <juce_core/juce_core.h>
// #define SAMPLE_BY_SAMPLE
#ifndef PI_F
#define PI_F 3.1415927410125732421875f
#endif
static inline float mtof(float m)
{
return powf(2, (m - 69.0f) / 12.0f) * 440.0f;
}
static inline bool float_equal(float one, float two) {
return abs(one - two) < 1e-5f;
}
void Shifter::Init()
{
volume = 1;
helm.setframesize(1024);
helm.setoverlap(1);
for (int i = 0; i < MAX_VOICES + 1; ++i)
{
out_midi[i] = -1;
}
for (int i = 0; i < BUFFER_SIZE; ++i)
{
in_buffer[i] = 0;
out_buffer[0][i] = 0;
out_buffer[1][i] = 0;
}
for (int i = 0; i < 8192; ++i) {
cos_lookup[i] = cos(2 * PI_F * i / 8192.0);
}
out_panning[MAX_VOICES] = 0.5f;
}
void Shifter::Process(const float* const* in,
float** out,
size_t size)
{
DetectPitch(in, out, size);
SetRates();
// for (size_t i = 0; i < size; ++i) {
// out[0][i] = 0;
// }
GetSamples(out, in[0], size);
//for (size_t i = 0; i < size; ++i)
//{
// // out[0][i] = osc.Process();
// // out[0][i] = in[0][i];
// //out[0][i] = out[0][i] + in[0][i];
// out[1][i] = out[0][i];
//}
// }
}
float findMedian(float a, float b, float c) {
if ((a >= b && a <= c) || (a <= b && a >= c))
return a;
else if ((b >= a && b <= c) || (b <= a && b >= c))
return b;
else
return c;
}
void Shifter::DetectPitch(const float* const* in, float** out, size_t size)
{
// detect current pitch
// pitch_detect.update(in[0], size);
// if(pitch_detect.available())
// {
// float read = pitch_detect.read();
// if(read >= 35 && read <= 2000)
// {
// for(int i = 2; i > 0; --i){
// last_freqs[i] = last_freqs[i-1];
// }
// last_freqs[0] = read;
// }
// }
// current_pitch = findMedian(last_freqs[0], last_freqs[1], last_freqs[2]);
// in_period = 1.0 / current_pitch * 48000;
helm.iosamples(in[0], out[0], size);
float period = helm.getperiod();
float fidel = helm.getfidelity();
//DBG("frequency: " << 48000 / period << " fidel: " << fidel);
// Adjustable filter amount (0.0f = no filtering, 1.0f = max filtering)
static float in_period_filter_amount = 0.7f; // You can expose this as a parameter
if (fidel > 0.95) {
// 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;
int midi = (int)(12 * log2f(in_freq / 440) + 69.5f);
out_midi[MAX_VOICES] = midi;
static float out_period_filter_amount = 0.7f; // You can expose this as a parameter
float target_out_period = 48000.0f / mtof(midi);
out_periods[MAX_VOICES] = out_periods[MAX_VOICES] * out_period_filter_amount + target_out_period * (1.0f - out_period_filter_amount);
}
void Shifter::SetRates() {}
float Shifter::GetOutputEnvelopePeriod(int out_voice) {
//TODO add something so that low pitch ratios end up reducing formant_preservation
return in_period * formant_preserve + out_periods[out_voice] * (1.0 - formant_preserve);
}
int Shifter::GetPeakIndex() {
int index = in_playhead - in_period * 2;
if (index < 0)
index += BUFFER_SIZE;
//search for max absolute value
int max_index = -1;
float max_value = -2;
for (int j = 0; j < in_period; ++j)
{
//float val = fabs(in_buffer[index]);
float val = in_buffer[index];
if (val > max_value)
{
max_index = index;
max_value = val;
}
if (++index >= BUFFER_SIZE)
{
index -= BUFFER_SIZE;
}
}
return max_index;
}
//void Shifter::AddInterpolatedFrame(int voice, int max_index, float resampling_period) {
// float period_ratio = resampling_period / out_periods[voice];
// float f_index;
// f_index = max_index - resampling_period;
// if (f_index < 0)
// {
// f_index += BUFFER_SIZE;
// }
// float mult = 0;
// int out_index = out_playhead;
// for (int j = 0; j < resampling_period * 2; ++j)
// {
// // mult = .5
// // * (1 - cosf(2 * PI_F * j / (period_to_use * 2 - 1)));
// float interp = f_index - (int)f_index;
// mult = .5
// * (1 - cos_lookup[(int)((float)j / (resampling_period * 2.0) * 8191.0)]);
// float value = ((1 - interp) * in_buffer[(int)f_index] + (interp)*in_buffer[(int)(f_index + 1) % 8192]) * mult;
// out_buffer[0][out_index] += value * (1 - out_panning[voice]);
// out_buffer[1][out_index] += value * out_panning[voice];
//
//
// f_index += period_ratio;
// if (f_index >= BUFFER_SIZE)
// {
// f_index -= BUFFER_SIZE;
// }
// if (++out_index >= BUFFER_SIZE)
// {
// out_index -= BUFFER_SIZE;
// }
// }
//}
void Shifter::AddInterpolatedFrame(int voice, int max_index, float resampling_period) {
float period_ratio = in_period / resampling_period;
float f_index;
f_index = max_index - in_period;
if (f_index < 0)
{
f_index += BUFFER_SIZE;
}
float mult = 0;
int out_index = out_playhead;
for (int j = 0; j < resampling_period * 2; ++j)
{
// mult = .5
// * (1 - cosf(2 * PI_F * j / (period_to_use * 2 - 1)));
float interp = f_index - (int)f_index;
mult = .5 * (1 - cos_lookup[(int)((float)j / (resampling_period * 2.0) * 8191.0)]);
float value = ((1 - interp) * in_buffer[(int)f_index] + (interp)*in_buffer[(int)(f_index + 1) % 8192]) * mult;
out_buffer[0][out_index] += value * (1 - out_panning[voice]);
out_buffer[1][out_index] += value * out_panning[voice];
f_index += period_ratio;
if (f_index >= BUFFER_SIZE)
{
f_index -= BUFFER_SIZE;
}
if (++out_index >= BUFFER_SIZE)
{
out_index -= BUFFER_SIZE;
}
}
}
void Shifter::GetSamples(float** output, const float* input, size_t size)
{
for (int i = 0; i < size; ++i)
{
//add new samples if necessary
for (int out_p = 0; out_p < MAX_VOICES + 1; ++out_p)
{
if (out_midi[out_p] == -1) continue;
if (out_period_counters[out_p] > out_periods[out_p])
{
out_period_counters[out_p] -= out_periods[out_p];
float resampling_period = GetOutputEnvelopePeriod(out_p);
//find the start index
int max_index = GetPeakIndex();
//add samples centered on that max
AddInterpolatedFrame(out_p, max_index, resampling_period);
}
}
//add input samples
in_buffer[in_playhead] = input[i];
//output samples, set to 0
for (int ch = 0; ch < 2; ++ch) {
output[ch][i] = out_buffer[ch][out_playhead];
out_buffer[ch][out_playhead] = 0;
}
//increment playheads
if (++in_playhead >= BUFFER_SIZE)
{
in_playhead -= BUFFER_SIZE;
}
if (++out_playhead >= BUFFER_SIZE)
{
out_playhead -= BUFFER_SIZE;
}
for (int out_p = 0; out_p < MAX_VOICES + 1; ++out_p)
{
if (out_midi[out_p] == -1) continue;
out_period_counters[out_p] += 1;
}
}
}
void Shifter::AddMidiNote(int note) {
for (int i = 0; i < MAX_VOICES; ++i) {
if (out_midi[i] == note) {
return;
}
}
for (int i = 0; i < MAX_VOICES; ++i) {
if (out_midi[i] == -1) {
out_midi[i] = note;
out_periods[i] = 48000.0f / mtof(note);
out_period_counters[i] = 0;
out_panning[i] = rand() / (float)RAND_MAX;
return;
}
}
}
void Shifter::RemoveMidiNote(int note) {
for (int i = 0; i < MAX_VOICES; ++i) {
if (out_midi[i] == note) {
out_midi[i] = -1;
return;
}
}
}

139
Source/Shifter.h Normal file
View File

@ -0,0 +1,139 @@
#ifndef SHIFTER_H
#define SHIFTER_H
#include "Helmholtz.h"
#define BUFFER_SIZE 8192
#define MAX_VOICES 12
template <typename T, size_t max_capacity>
class circ_queue {
public:
circ_queue() {
head = buffer;
tail = buffer;
size = 0;
capacity = max_capacity;
}
void push(T val) {
if (size == max_capacity) {
pop();
}
*tail = val;
if (++tail >= buffer + max_capacity) {
tail -= max_capacity;
}
size++;
//*head = val;
}
void clear() {
head = buffer;
tail = buffer;
size = 0;
}
T pop() {
if (size > 0) {
T to_ret = *head;
if (++head >= buffer + max_capacity) {
head -= max_capacity;
}
--size;
return to_ret;
}
else {
return T();
}
}
T& get(int indx) {
T* ret_ptr = head + indx;
if (ret_ptr >= buffer + max_capacity) {
ret_ptr -= max_capacity;
}
return *ret_ptr;
}
T& operator[](int indx) {
T* ret_ptr = head + indx;
if (ret_ptr >= buffer + max_capacity) {
ret_ptr -= max_capacity;
}
return *ret_ptr;
}
size_t capacity;
// int size() { return capacity; }
size_t size;
T buffer[max_capacity];
T* head;
T* tail;
};
class Shifter {
public:
void Init();
void Process(const float* const* in,
float** out,
size_t size);
void AddMidiNote(int note);
void RemoveMidiNote(int note);
void SetFormantPreserve(float val) { formant_preserve = val; }
int out_midi[MAX_VOICES + 1] = { -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 };
private:
void DetectPitch(const float* const* in, float** out, size_t size);
void SetRates();
void GetSamples(float** output, const float* input, size_t size);
float GetOutputEnvelopePeriod(int out_voice);
int GetPeakIndex();
void AddInterpolatedFrame(int voice, int max_index, float period_to_use);
Helmholtz helm;
// GranularSustain player;
int selected_sample;
bool playing;
bool looping;
bool loop_engaged;
float play_head;
float rate_factor;
float sample_freq = 440;
float freq_target = 440;
float last_freq = 440;
double current_pitch = 440;
double smear_thresh = .085;
double smear_step = 0.003;
bool onset_trigger = false;
bool pitch_trigger = false;
int trigger_bank;
circ_queue<float, 3> prev_freqs;
float formant_preserve = 0;
float volume;
float pitch_adj;
bool last_record;
int record_playhead;
bool dry_wet = false;
bool is_pitched = true;
float last_freqs[3];
float in_buffer[BUFFER_SIZE];
float out_buffer[2][BUFFER_SIZE];
int out_playhead = 0;
int in_playhead = 0;
float out_periods[MAX_VOICES + 1] = { 0,0,0,0,0,0,0,0,0,0,0,0 }; //C3
float out_panning[MAX_VOICES + 1] = { 0,0,0,0,0,0,0,0,0,0,0,.5 }; //C3
float in_period = 366.936;
float out_period_counters[MAX_VOICES + 1] = { 0,0,0,0,0,0,0,0,0,0,0,0 };
float cos_lookup[8192];
};
#endif

657
Source/WebViewPluginDemo.h Normal file
View File

@ -0,0 +1,657 @@
/*
==============================================================================
This file is part of the JUCE framework examples.
Copyright (c) Raw Material Software Limited
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
to use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
==============================================================================
*/
/*******************************************************************************
The block below describes the properties of this PIP. A PIP is a short snippet
of code that can be read by the Projucer and used to generate a JUCE project.
BEGIN_JUCE_PIP_METADATA
name: WebViewPluginDemo
version: 1.0.0
vendor: JUCE
website: http://juce.com
description: Filtering audio plugin using an HTML/JS user interface
dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats,
juce_audio_plugin_client, juce_audio_processors, juce_dsp,
juce_audio_utils, juce_core, juce_data_structures,
juce_events, juce_graphics, juce_gui_basics, juce_gui_extra
exporters: xcode_mac, vs2022, linux_make, androidstudio, xcode_iphone
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1, JUCE_USE_WIN_WEBVIEW2_WITH_STATIC_LINKING=1
type: AudioProcessor
mainClass: WebViewPluginAudioProcessorWrapper
useLocalCopy: 1
END_JUCE_PIP_METADATA
*******************************************************************************/
#pragma once
#include "DemoUtilities.h"
#include <JuceHeader.h>
#include "Shifter.h"
//using namespace juce::dsp;
namespace ID
{
#define PARAMETER_ID(str) static const ParameterID str { #str, 1 };
PARAMETER_ID(formantPreserve)
PARAMETER_ID(mute)
PARAMETER_ID(filterType)
#undef PARAMETER_ID
}
class CircularBuffer
{
public:
CircularBuffer(int numChannels, int numSamples)
: buffer(data, (size_t)numChannels, (size_t)numSamples)
{
}
template <typename T>
void push(dsp::AudioBlock<T> b)
{
jassert(b.getNumChannels() == buffer.getNumChannels());
const auto trimmed = b.getSubBlock(b.getNumSamples()
- std::min(b.getNumSamples(), buffer.getNumSamples()));
const auto bufferLength = (int64)buffer.getNumSamples();
for (auto samplesRemaining = (int64)trimmed.getNumSamples(); samplesRemaining > 0;)
{
const auto writeOffset = writeIx % bufferLength;
const auto numSamplesToWrite = std::min(samplesRemaining, bufferLength - writeOffset);
auto destSubBlock = buffer.getSubBlock((size_t)writeOffset, (size_t)numSamplesToWrite);
const auto sourceSubBlock = trimmed.getSubBlock(trimmed.getNumSamples() - (size_t)samplesRemaining,
(size_t)numSamplesToWrite);
destSubBlock.copyFrom(sourceSubBlock);
samplesRemaining -= numSamplesToWrite;
writeIx += numSamplesToWrite;
}
}
template <typename T>
void push(Span<T> s)
{
auto* ptr = s.begin();
dsp::AudioBlock<T> b(&ptr, 1, s.size());
push(b);
}
void read(int64 readIx, dsp::AudioBlock<float> output) const
{
const auto numChannelsToUse = std::min(buffer.getNumChannels(), output.getNumChannels());
jassert(output.getNumChannels() == buffer.getNumChannels());
const auto bufferLength = (int64)buffer.getNumSamples();
for (auto outputOffset = (size_t)0; outputOffset < output.getNumSamples();)
{
const auto inputOffset = (size_t)((readIx + (int64)outputOffset) % bufferLength);
const auto numSamplesToRead = std::min(output.getNumSamples() - outputOffset,
(size_t)bufferLength - inputOffset);
auto destSubBlock = output.getSubBlock(outputOffset, numSamplesToRead)
.getSubsetChannelBlock(0, numChannelsToUse);
destSubBlock.copyFrom(buffer.getSubBlock(inputOffset, numSamplesToRead)
.getSubsetChannelBlock(0, numChannelsToUse));
outputOffset += numSamplesToRead;
}
}
int64 getWriteIndex() const noexcept { return writeIx; }
private:
HeapBlock<char> data;
dsp::AudioBlock<float> buffer;
int64 writeIx = 0;
};
class SpectralBars
{
public:
//template <typename T>
void push(int data)
{
testQueue.push(data);
}
void compute(Span<int> output) {
int index = 0;
for (auto it = output.begin(); it != output.end(); ++it) {
*it = testQueue.get(index++);
}
}
private:
circ_queue<int, 256> testQueue;
};
//==============================================================================
class WebViewPluginAudioProcessor : public AudioProcessor
{
public:
//==============================================================================
WebViewPluginAudioProcessor(AudioProcessorValueTreeState::ParameterLayout layout);
//==============================================================================
void prepareToPlay(double sampleRate, int samplesPerBlock) override;
void releaseResources() override {}
bool isBusesLayoutSupported(const BusesLayout& layouts) const override;
void processBlock(AudioBuffer<float>&, MidiBuffer&) override;
using AudioProcessor::processBlock;
//==============================================================================
const String getName() const override { return JucePlugin_Name; }
bool acceptsMidi() const override { return false; }
bool producesMidi() const override { return false; }
bool isMidiEffect() const override { return false; }
double getTailLengthSeconds() const override { return 0.0; }
//==============================================================================
int getNumPrograms() override { return 1; }
int getCurrentProgram() override { return 0; }
void setCurrentProgram(int) override {}
const String getProgramName(int) override { return {}; }
void changeProgramName(int, const String&) override {}
//==============================================================================
void getStateInformation(MemoryBlock& destData) override;
void setStateInformation(const void* data, int sizeInBytes) override;
struct Parameters
{
public:
explicit Parameters(AudioProcessorValueTreeState::ParameterLayout& layout)
: formantPreserve(addToLayout<AudioParameterFloat>(layout,
ID::formantPreserve,
"Formant Preserve",
NormalisableRange<float> {0.0f, 1.0f, .01f},
.5f)),
autoTuneSpeed(addToLayout<AudioParameterFloat>(layout,
ID::formantPreserve,
"AutoTuneSpeed",
NormalisableRange<float> {0.0f, 1.0f, .01f},
.5f)),
mute(addToLayout<AudioParameterBool>(layout, ID::mute, "Mute", false)),
filterType(addToLayout<AudioParameterChoice>(layout,
ID::filterType,
"Filter type",
StringArray{ "Low-pass", "High-pass", "Band-pass" },
0))
{
}
AudioParameterFloat& formantPreserve;
AudioParameterFloat& autoTuneSpeed;
AudioParameterBool& mute;
AudioParameterChoice& filterType;
private:
template <typename Param>
static void add(AudioProcessorParameterGroup& group, std::unique_ptr<Param> param)
{
group.addChild(std::move(param));
}
template <typename Param>
static void add(AudioProcessorValueTreeState::ParameterLayout& group, std::unique_ptr<Param> param)
{
group.add(std::move(param));
}
template <typename Param, typename Group, typename... Ts>
static Param& addToLayout(Group& layout, Ts&&... ts)
{
auto param = std::make_unique<Param>(std::forward<Ts>(ts)...);
auto& ref = *param;
add(layout, std::move(param));
return ref;
}
};
Parameters parameters;
AudioProcessorValueTreeState state;
std::vector<int> spectrumData = [] { return std::vector<int>(256, 0.0f); }();
SpinLock spectrumDataLock;
SpectralBars spectralBars;
dsp::LadderFilter<float> filter;
private:
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(WebViewPluginAudioProcessor)
Shifter shifter;
};
//==============================================================================
WebViewPluginAudioProcessor::WebViewPluginAudioProcessor(AudioProcessorValueTreeState::ParameterLayout layout)
: AudioProcessor(BusesProperties()
#if ! JucePlugin_IsMidiEffect
#if ! JucePlugin_IsSynth
.withInput("Input", juce::AudioChannelSet::stereo(), true)
#endif
.withOutput("Output", juce::AudioChannelSet::stereo(), true)
#endif
),
parameters(layout),
state(*this, nullptr, "STATE", std::move(layout))
{
shifter.Init();
}
//==============================================================================
void WebViewPluginAudioProcessor::prepareToPlay(double sampleRate, int samplesPerBlock)
{
const auto channels = std::max(getTotalNumInputChannels(), getTotalNumOutputChannels());
if (channels == 0)
return;
filter.prepare({ sampleRate, (uint32_t)samplesPerBlock, (uint32_t)channels });
filter.reset();
}
bool WebViewPluginAudioProcessor::isBusesLayoutSupported(const BusesLayout& layouts) const
{
if (layouts.getMainOutputChannelSet() != juce::AudioChannelSet::mono()
&& layouts.getMainOutputChannelSet() != juce::AudioChannelSet::stereo())
return false;
if (layouts.getMainOutputChannelSet() != layouts.getMainInputChannelSet())
return false;
return true;
}
void WebViewPluginAudioProcessor::processBlock(juce::AudioBuffer<float>& buffer,
juce::MidiBuffer& midi)
{
juce::ScopedNoDenormals noDenormals;
const auto totalNumInputChannels = getTotalNumInputChannels();
const auto totalNumOutputChannels = getTotalNumOutputChannels();
for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i)
buffer.clear(i, 0, buffer.getNumSamples());
shifter.SetFormantPreserve(parameters.formantPreserve.get());
juce::AudioBuffer<float> const_buff;
const_buff.makeCopyOf(buffer);
shifter.Process(const_buff.getArrayOfReadPointers(), (float**)buffer.getArrayOfWritePointers(), buffer.getNumSamples());
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());
}
{
//DBG(shifter.out_midi[MAX_VOICES]);
//push midi note
spectralBars.push(shifter.out_midi[MAX_VOICES]);
const SpinLock::ScopedTryLockType lock(spectrumDataLock);
if (!lock.isLocked())
return;
spectralBars.compute({ spectrumData.data(), spectrumData.size() });
}
/*for(auto i = 0; i < buffer.getNumSamples(); ++i)
{
bool process = (i % 256) == 0 && i != 0;
for(auto j = 0; j < totalNumInputChannels; ++j)
{
input[j][i] = buffer.getReadPointer(j)[i];
}
}
filter.setCutoffFrequencyHz (parameters.cutoffFreqHz.get());
const auto filterMode = [this]
{
switch (parameters.filterType.getIndex())
{
case 0:
return dsp::LadderFilter<float>::Mode::LPF12;
case 1:
return dsp::LadderFilter<float>::Mode::HPF12;
default:
return dsp::LadderFilter<float>::Mode::BPF12;
}
}();
filter.setMode (filterMode);
auto outBlock = dsp::AudioBlock<float> { buffer }.getSubsetChannelBlock (0, (size_t) getTotalNumOutputChannels());
if (parameters.mute.get())
outBlock.clear();
filter.process (dsp::ProcessContextReplacing<float> (outBlock));
spectralBars.push (Span { buffer.getReadPointer (0), (size_t) buffer.getNumSamples() });
{
const SpinLock::ScopedTryLockType lock (spectrumDataLock);
if (! lock.isLocked())
return;
spectralBars.compute ({ spectrumData.data(), spectrumData.size() });
}*/
}
//==============================================================================
void WebViewPluginAudioProcessor::getStateInformation(juce::MemoryBlock& destData)
{
juce::ignoreUnused(destData);
}
void WebViewPluginAudioProcessor::setStateInformation(const void* data, int sizeInBytes)
{
juce::ignoreUnused(data, sizeInBytes);
}
extern const String localDevServerAddress;
std::optional<WebBrowserComponent::Resource> getResource(const String& url);
//==============================================================================
struct SinglePageBrowser : WebBrowserComponent
{
using WebBrowserComponent::WebBrowserComponent;
// Prevent page loads from navigating away from our single page web app
bool pageAboutToLoad(const String& newURL) override;
};
//==============================================================================
class WebViewPluginAudioProcessorEditor : public AudioProcessorEditor, private Timer
{
public:
explicit WebViewPluginAudioProcessorEditor(WebViewPluginAudioProcessor&);
std::optional<WebBrowserComponent::Resource> getResource(const String& url);
//==============================================================================
void paint(Graphics&) override;
void resized() override;
int getControlParameterIndex(Component&) override
{
return controlParameterIndexReceiver.getControlParameterIndex();
}
void timerCallback() override
{
static constexpr size_t numFramesBuffered = 5;
SpinLock::ScopedLockType lock{ processorRef.spectrumDataLock };
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)
{*/
webComponent.emitEventIfBrowserIsVisible("spectrumData", var{});
//}
}
private:
WebViewPluginAudioProcessor& processorRef;
WebSliderRelay formantSliderRelay{ "formantSlider" };
WebToggleButtonRelay muteToggleRelay{ "muteToggle" };
WebComboBoxRelay filterTypeComboRelay{ "filterTypeCombo" };
WebControlParameterIndexReceiver controlParameterIndexReceiver;
SinglePageBrowser webComponent{ WebBrowserComponent::Options{}
.withBackend(WebBrowserComponent::Options::Backend::webview2)
.withWinWebView2Options(WebBrowserComponent::Options::WinWebView2{}
.withUserDataFolder(File::getSpecialLocation(File::SpecialLocationType::tempDirectory)))
.withNativeIntegrationEnabled()
.withOptionsFrom(formantSliderRelay)
.withOptionsFrom(muteToggleRelay)
.withOptionsFrom(filterTypeComboRelay)
.withOptionsFrom(controlParameterIndexReceiver)
.withNativeFunction("sayHello", [](auto& var, auto complete)
{
complete("Hello " + var[0].toString());
})
.withResourceProvider([this](const auto& url)
{
return getResource(url);
},
URL { localDevServerAddress }.getOrigin()) };
WebSliderParameterAttachment formantAttachment;
WebToggleButtonParameterAttachment muteAttachment;
WebComboBoxParameterAttachment filterTypeAttachment;
std::deque<Array<var>> spectrumDataFrames;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(WebViewPluginAudioProcessorEditor)
};
static ZipFile* getZipFile()
{
static auto stream = createAssetInputStream("webviewplugin-gui_1.0.0.zip", AssertAssetExists::no);
if (stream == nullptr)
return nullptr;
static ZipFile f{ stream.get(), false };
return &f;
}
static const char* getMimeForExtension(const String& extension)
{
static const std::unordered_map<String, const char*> mimeMap =
{
{ { "htm" }, "text/html" },
{ { "html" }, "text/html" },
{ { "txt" }, "text/plain" },
{ { "jpg" }, "image/jpeg" },
{ { "jpeg" }, "image/jpeg" },
{ { "svg" }, "image/svg+xml" },
{ { "ico" }, "image/vnd.microsoft.icon" },
{ { "json" }, "application/json" },
{ { "png" }, "image/png" },
{ { "css" }, "text/css" },
{ { "map" }, "application/json" },
{ { "js" }, "text/javascript" },
{ { "woff2" }, "font/woff2" }
};
if (const auto it = mimeMap.find(extension.toLowerCase()); it != mimeMap.end())
return it->second;
jassertfalse;
return "";
}
static String getExtension(String filename)
{
return filename.fromLastOccurrenceOf(".", false, false);
}
static auto streamToVector(InputStream& stream)
{
std::vector<std::byte> result((size_t)stream.getTotalLength());
stream.setPosition(0);
[[maybe_unused]] const auto bytesRead = stream.read(result.data(), result.size());
jassert(bytesRead == (ssize_t)result.size());
return result;
}
std::optional<WebBrowserComponent::Resource> WebViewPluginAudioProcessorEditor::getResource(const String& url)
{
const auto urlToRetrive = url == "/" ? String{ "index.html" }
: url.fromFirstOccurrenceOf("/", false, false);
if (auto* archive = getZipFile())
{
if (auto* entry = archive->getEntry(urlToRetrive))
{
auto stream = rawToUniquePtr(archive->createStreamForEntry(*entry));
auto v = streamToVector(*stream);
auto mime = getMimeForExtension(getExtension(entry->filename).toLowerCase());
return WebBrowserComponent::Resource{ std::move(v),
std::move(mime) };
}
}
if (urlToRetrive == "index.html")
{
auto fallbackIndexHtml = createAssetInputStream("webviewplugin-gui-fallback.html");
return WebBrowserComponent::Resource{ streamToVector(*fallbackIndexHtml),
String { "text/html" } };
}
if (urlToRetrive == "data.txt")
{
WebBrowserComponent::Resource resource;
static constexpr char testData[] = "testdata";
MemoryInputStream stream{ testData, numElementsInArray(testData) - 1, false };
return WebBrowserComponent::Resource{ streamToVector(stream), String { "text/html" } };
}
if (urlToRetrive == "spectrumData.json")
{
Array<var> frames;
for (const auto& frame : spectrumDataFrames)
frames.add(frame);
DynamicObject::Ptr d(new DynamicObject());
d->setProperty("timeResolutionMs", getTimerInterval());
d->setProperty("frames", std::move(frames));
const auto s = JSON::toString(d.get());
MemoryInputStream stream{ s.getCharPointer(), s.getNumBytesAsUTF8(), false };
return WebBrowserComponent::Resource{ streamToVector(stream), String { "application/json" } };
}
return std::nullopt;
}
#if JUCE_ANDROID
// The localhost is available on this address to the emulator
const String localDevServerAddress = "http://10.0.2.2:3000/";
#else
const String localDevServerAddress = "http://localhost:3000/";
#endif
bool SinglePageBrowser::pageAboutToLoad(const String& newURL)
{
return newURL == localDevServerAddress || newURL == getResourceProviderRoot();
}
//==============================================================================
WebViewPluginAudioProcessorEditor::WebViewPluginAudioProcessorEditor(WebViewPluginAudioProcessor& p)
: AudioProcessorEditor(&p), processorRef(p),
formantAttachment(*processorRef.state.getParameter(ID::formantPreserve.getParamID()),
formantSliderRelay,
processorRef.state.undoManager),
muteAttachment(*processorRef.state.getParameter(ID::mute.getParamID()),
muteToggleRelay,
processorRef.state.undoManager),
filterTypeAttachment(*processorRef.state.getParameter(ID::filterType.getParamID()),
filterTypeComboRelay,
processorRef.state.undoManager)
{
addAndMakeVisible(webComponent);
webComponent.goToURL(localDevServerAddress);
//webComponent.goToURL (WebBrowserComponent::getResourceProviderRoot());
setSize(500, 500);
startTimerHz(20);
}
//==============================================================================
void WebViewPluginAudioProcessorEditor::paint(Graphics& g)
{
// (Our component is opaque, so we must completely fill the background with a solid colour)
g.fillAll(getLookAndFeel().findColour(ResizableWindow::backgroundColourId));
}
void WebViewPluginAudioProcessorEditor::resized()
{
webComponent.setBounds(getLocalBounds());
}
class WebViewPluginAudioProcessorWrapper : public WebViewPluginAudioProcessor
{
public:
WebViewPluginAudioProcessorWrapper() : WebViewPluginAudioProcessor({})
{
}
bool hasEditor() const override { return true; }
AudioProcessorEditor* createEditor() override { return new WebViewPluginAudioProcessorEditor(*this); }
};

419
Source/mayer_fft.cpp Normal file
View File

@ -0,0 +1,419 @@
/* This is the FFT routine taken from PureData, a great piece of
software by Miller S. Puckette.
http://crca.ucsd.edu/~msp/software.html */
/*
** FFT and FHT routines
** Copyright 1988, 1993; Ron Mayer
**
** mayer_fht(fz,n);
** Does a hartley transform of "n" points in the array "fz".
** mayer_fft(n,real,imag)
** Does a fourier transform of "n" points of the "real" and
** "imag" arrays.
** mayer_ifft(n,real,imag)
** Does an inverse fourier transform of "n" points of the "real"
** and "imag" arrays.
** mayer_realfft(n,real)
** Does a real-valued fourier transform of "n" points of the
** "real" array. The real part of the transform ends
** up in the first half of the array and the imaginary part of the
** transform ends up in the second half of the array.
** mayer_realifft(n,real)
** The inverse of the realfft() routine above.
**
**
** NOTE: This routine uses at least 2 patented algorithms, and may be
** under the restrictions of a bunch of different organizations.
** Although I wrote it completely myself, it is kind of a derivative
** of a routine I once authored and released under the GPL, so it
** may fall under the free software foundation's restrictions;
** it was worked on as a Stanford Univ project, so they claim
** some rights to it; it was further optimized at work here, so
** I think this company claims parts of it. The patents are
** held by R. Bracewell (the FHT algorithm) and O. Buneman (the
** trig generator), both at Stanford Univ.
** If it were up to me, I'd say go do whatever you want with it;
** but it would be polite to give credit to the following people
** if you use this anywhere:
** Euler - probable inventor of the fourier transform.
** Gauss - probable inventor of the FFT.
** Hartley - probable inventor of the hartley transform.
** Buneman - for a really cool trig generator
** Mayer(me) - for authoring this particular version and
** including all the optimizations in one package.
** Thanks,
** Ron Mayer; mayer@acuson.com
**
*/
/* This is a slightly modified version of Mayer's contribution; write
* msp@ucsd.edu for the original code. Kudos to Mayer for a fine piece
* of work. -msp
*/
#define REAL float
#define GOOD_TRIG
#ifdef GOOD_TRIG
#else
#define FAST_TRIG
#endif
#if defined(GOOD_TRIG)
#define FHT_SWAP(a,b,t) {(t)=(a);(a)=(b);(b)=(t);}
#define TRIG_VARS \
int t_lam=0;
#define TRIG_INIT(k,c,s) \
{ \
int i; \
for (i=2 ; i<=k ; i++) \
{coswrk[i]=costab[i];sinwrk[i]=sintab[i];} \
t_lam = 0; \
c = 1; \
s = 0; \
}
#define TRIG_NEXT(k,c,s) \
{ \
int i,j; \
(t_lam)++; \
for (i=0 ; !((1<<i)&t_lam) ; i++); \
i = k-i; \
s = sinwrk[i]; \
c = coswrk[i]; \
if (i>1) \
{ \
for (j=k-i+2 ; (1<<j)&t_lam ; j++); \
j = k - j; \
sinwrk[i] = halsec[i] * (sinwrk[i-1] + sinwrk[j]); \
coswrk[i] = halsec[i] * (coswrk[i-1] + coswrk[j]); \
} \
}
#define TRIG_RESET(k,c,s)
#endif
#if defined(FAST_TRIG)
#define TRIG_VARS \
REAL t_c,t_s;
#define TRIG_INIT(k,c,s) \
{ \
t_c = costab[k]; \
t_s = sintab[k]; \
c = 1; \
s = 0; \
}
#define TRIG_NEXT(k,c,s) \
{ \
REAL t = c; \
c = t*t_c - s*t_s; \
s = t*t_s + s*t_c; \
}
#define TRIG_RESET(k,c,s)
#endif
static REAL halsec[20] =
{
0,
0,
.54119610014619698439972320536638942006107206337801,
.50979557910415916894193980398784391368261849190893,
.50241928618815570551167011928012092247859337193963,
.50060299823519630134550410676638239611758632599591,
.50015063602065098821477101271097658495974913010340,
.50003765191554772296778139077905492847503165398345,
.50000941253588775676512870469186533538523133757983,
.50000235310628608051401267171204408939326297376426,
.50000058827484117879868526730916804925780637276181,
.50000014706860214875463798283871198206179118093251,
.50000003676714377807315864400643020315103490883972,
.50000000919178552207366560348853455333939112569380,
.50000000229794635411562887767906868558991922348920,
.50000000057448658687873302235147272458812263401372
};
static REAL costab[20] =
{
.00000000000000000000000000000000000000000000000000,
.70710678118654752440084436210484903928483593768847,
.92387953251128675612818318939678828682241662586364,
.98078528040323044912618223613423903697393373089333,
.99518472667219688624483695310947992157547486872985,
.99879545620517239271477160475910069444320361470461,
.99969881869620422011576564966617219685006108125772,
.99992470183914454092164649119638322435060646880221,
.99998117528260114265699043772856771617391725094433,
.99999529380957617151158012570011989955298763362218,
.99999882345170190992902571017152601904826792288976,
.99999970586288221916022821773876567711626389934930,
.99999992646571785114473148070738785694820115568892,
.99999998161642929380834691540290971450507605124278,
.99999999540410731289097193313960614895889430318945,
.99999999885102682756267330779455410840053741619428
};
static REAL sintab[20] =
{
1.0000000000000000000000000000000000000000000000000,
.70710678118654752440084436210484903928483593768846,
.38268343236508977172845998403039886676134456248561,
.19509032201612826784828486847702224092769161775195,
.09801714032956060199419556388864184586113667316749,
.04906767432741801425495497694268265831474536302574,
.02454122852291228803173452945928292506546611923944,
.01227153828571992607940826195100321214037231959176,
.00613588464915447535964023459037258091705788631738,
.00306795676296597627014536549091984251894461021344,
.00153398018628476561230369715026407907995486457522,
.00076699031874270452693856835794857664314091945205,
.00038349518757139558907246168118138126339502603495,
.00019174759731070330743990956198900093346887403385,
.00009587379909597734587051721097647635118706561284,
.00004793689960306688454900399049465887274686668768
};
static REAL coswrk[20] =
{
.00000000000000000000000000000000000000000000000000,
.70710678118654752440084436210484903928483593768847,
.92387953251128675612818318939678828682241662586364,
.98078528040323044912618223613423903697393373089333,
.99518472667219688624483695310947992157547486872985,
.99879545620517239271477160475910069444320361470461,
.99969881869620422011576564966617219685006108125772,
.99992470183914454092164649119638322435060646880221,
.99998117528260114265699043772856771617391725094433,
.99999529380957617151158012570011989955298763362218,
.99999882345170190992902571017152601904826792288976,
.99999970586288221916022821773876567711626389934930,
.99999992646571785114473148070738785694820115568892,
.99999998161642929380834691540290971450507605124278,
.99999999540410731289097193313960614895889430318945,
.99999999885102682756267330779455410840053741619428
};
static REAL sinwrk[20] =
{
1.0000000000000000000000000000000000000000000000000,
.70710678118654752440084436210484903928483593768846,
.38268343236508977172845998403039886676134456248561,
.19509032201612826784828486847702224092769161775195,
.09801714032956060199419556388864184586113667316749,
.04906767432741801425495497694268265831474536302574,
.02454122852291228803173452945928292506546611923944,
.01227153828571992607940826195100321214037231959176,
.00613588464915447535964023459037258091705788631738,
.00306795676296597627014536549091984251894461021344,
.00153398018628476561230369715026407907995486457522,
.00076699031874270452693856835794857664314091945205,
.00038349518757139558907246168118138126339502603495,
.00019174759731070330743990956198900093346887403385,
.00009587379909597734587051721097647635118706561284,
.00004793689960306688454900399049465887274686668768
};
#define SQRT2_2 0.70710678118654752440084436210484
#define SQRT2 2*0.70710678118654752440084436210484
void mayer_fht(REAL* fz, int n)
{
/* REAL a,b;
REAL c1,s1,s2,c2,s3,c3,s4,c4;
REAL f0,g0,f1,g1,f2,g2,f3,g3; */
int k, k1, k2, k3, k4, kx;
REAL* fi, * fn, * gi;
TRIG_VARS;
for (k1 = 1, k2 = 0; k1 < n; k1++)
{
REAL aa;
for (k = n >> 1; (!((k2 ^= k) & k)); k >>= 1);
if (k1 > k2)
{
aa = fz[k1]; fz[k1] = fz[k2]; fz[k2] = aa;
}
}
for (k = 0; (1 << k) < n; k++);
k &= 1;
if (k == 0)
{
for (fi = fz, fn = fz + n; fi < fn; fi += 4)
{
REAL f0, f1, f2, f3;
f1 = fi[0] - fi[1];
f0 = fi[0] + fi[1];
f3 = fi[2] - fi[3];
f2 = fi[2] + fi[3];
fi[2] = (f0 - f2);
fi[0] = (f0 + f2);
fi[3] = (f1 - f3);
fi[1] = (f1 + f3);
}
}
else
{
for (fi = fz, fn = fz + n, gi = fi + 1; fi < fn; fi += 8, gi += 8)
{
REAL bs1, bc1, bs2, bc2, bs3, bc3, bs4, bc4,
bg0, bf0, bf1, bg1, bf2, bg2, bf3, bg3;
bc1 = fi[0] - gi[0];
bs1 = fi[0] + gi[0];
bc2 = fi[2] - gi[2];
bs2 = fi[2] + gi[2];
bc3 = fi[4] - gi[4];
bs3 = fi[4] + gi[4];
bc4 = fi[6] - gi[6];
bs4 = fi[6] + gi[6];
bf1 = (bs1 - bs2);
bf0 = (bs1 + bs2);
bg1 = (bc1 - bc2);
bg0 = (bc1 + bc2);
bf3 = (bs3 - bs4);
bf2 = (bs3 + bs4);
bg3 = SQRT2 * bc4;
bg2 = SQRT2 * bc3;
fi[4] = bf0 - bf2;
fi[0] = bf0 + bf2;
fi[6] = bf1 - bf3;
fi[2] = bf1 + bf3;
gi[4] = bg0 - bg2;
gi[0] = bg0 + bg2;
gi[6] = bg1 - bg3;
gi[2] = bg1 + bg3;
}
}
if (n < 16) return;
do
{
REAL s1, c1;
int ii;
k += 2;
k1 = 1 << k;
k2 = k1 << 1;
k4 = k2 << 1;
k3 = k2 + k1;
kx = k1 >> 1;
fi = fz;
gi = fi + kx;
fn = fz + n;
do
{
REAL g0, f0, f1, g1, f2, g2, f3, g3;
f1 = fi[0] - fi[k1];
f0 = fi[0] + fi[k1];
f3 = fi[k2] - fi[k3];
f2 = fi[k2] + fi[k3];
fi[k2] = f0 - f2;
fi[0] = f0 + f2;
fi[k3] = f1 - f3;
fi[k1] = f1 + f3;
g1 = gi[0] - gi[k1];
g0 = gi[0] + gi[k1];
g3 = SQRT2 * gi[k3];
g2 = SQRT2 * gi[k2];
gi[k2] = g0 - g2;
gi[0] = g0 + g2;
gi[k3] = g1 - g3;
gi[k1] = g1 + g3;
gi += k4;
fi += k4;
} while (fi < fn);
TRIG_INIT(k, c1, s1);
for (ii = 1; ii < kx; ii++)
{
REAL c2, s2;
TRIG_NEXT(k, c1, s1);
c2 = c1 * c1 - s1 * s1;
s2 = 2 * (c1 * s1);
fn = fz + n;
fi = fz + ii;
gi = fz + k1 - ii;
do
{
REAL a, b, g0, f0, f1, g1, f2, g2, f3, g3;
b = s2 * fi[k1] - c2 * gi[k1];
a = c2 * fi[k1] + s2 * gi[k1];
f1 = fi[0] - a;
f0 = fi[0] + a;
g1 = gi[0] - b;
g0 = gi[0] + b;
b = s2 * fi[k3] - c2 * gi[k3];
a = c2 * fi[k3] + s2 * gi[k3];
f3 = fi[k2] - a;
f2 = fi[k2] + a;
g3 = gi[k2] - b;
g2 = gi[k2] + b;
b = s1 * f2 - c1 * g3;
a = c1 * f2 + s1 * g3;
fi[k2] = f0 - a;
fi[0] = f0 + a;
gi[k3] = g1 - b;
gi[k1] = g1 + b;
b = c1 * g2 - s1 * f3;
a = s1 * g2 + c1 * f3;
gi[k2] = g0 - a;
gi[0] = g0 + a;
fi[k3] = f1 - b;
fi[k1] = f1 + b;
gi += k4;
fi += k4;
} while (fi < fn);
}
TRIG_RESET(k, c1, s1);
} while (k4 < n);
}
void mayer_fft(int n, REAL* real, REAL* imag)
{
REAL a, b, c, d;
REAL q, r, s, t;
int i, j, k;
for (i = 1, j = n - 1, k = n / 2; i < k; i++, j--) {
a = real[i]; b = real[j]; q = a + b; r = a - b;
c = imag[i]; d = imag[j]; s = c + d; t = c - d;
real[i] = (q + t) * .5; real[j] = (q - t) * .5;
imag[i] = (s - r) * .5; imag[j] = (s + r) * .5;
}
mayer_fht(real, n);
mayer_fht(imag, n);
}
void mayer_ifft(int n, REAL* real, REAL* imag)
{
REAL a, b, c, d;
REAL q, r, s, t;
int i, j, k;
mayer_fht(real, n);
mayer_fht(imag, n);
for (i = 1, j = n - 1, k = n / 2; i < k; i++, j--) {
a = real[i]; b = real[j]; q = a + b; r = a - b;
c = imag[i]; d = imag[j]; s = c + d; t = c - d;
imag[i] = (s + r) * 0.5; imag[j] = (s - r) * 0.5;
real[i] = (q - t) * 0.5; real[j] = (q + t) * 0.5;
}
}
void mayer_realfft(int n, REAL* real)
{
REAL a, b;
int i, j, k;
mayer_fht(real, n);
for (i = 1, j = n - 1, k = n / 2; i < k; i++, j--) {
a = real[i];
b = real[j];
real[j] = (a - b) * 0.5;
real[i] = (a + b) * 0.5;
}
}
void mayer_realifft(int n, REAL* real)
{
REAL a, b;
int i, j, k;
for (i = 1, j = n - 1, k = n / 2; i < k; i++, j--) {
a = real[i];
b = real[j];
real[j] = (a - b);
real[i] = (a + b);
}
mayer_fht(real, n);
}

9
Source/mayer_fft.h Normal file
View File

@ -0,0 +1,9 @@
#ifndef MAYER_H
#define MAYER_H
#define REAL float
void mayer_realfft(int n, REAL* real);
void mayer_realifft(int n, REAL* real);
#endif