initial
This commit is contained in:
232
Source/DemoUtilities.h
Normal file
232
Source/DemoUtilities.h
Normal 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
271
Source/Helmholtz.cpp
Normal 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
109
Source/Helmholtz.h
Normal 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
16
Source/Main.cpp
Normal 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
285
Source/Shifter.cpp
Normal 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
139
Source/Shifter.h
Normal 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
657
Source/WebViewPluginDemo.h
Normal 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
419
Source/mayer_fft.cpp
Normal 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
9
Source/mayer_fft.h
Normal 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
|
||||
Reference in New Issue
Block a user