Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pole-zero: pull request branch for supporting pole-zero plots #94

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions modules/foleys_gui_magic/General/foleys_MagicJUCEFactories.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -600,7 +600,10 @@ class XYDraggerItem : public GuiItem

static const juce::Identifier pCrosshair;
static const juce::StringArray pCrosshairTypes;
static const juce::Identifier pDotType;
static const juce::StringArray pDotTypes;
static const juce::Identifier pRadius;
static const juce::Identifier pLineThickness;
static const juce::Identifier pWheelParameter;
static const juce::Identifier pContextParameter;
static const juce::Identifier pSenseFactor;
Expand Down Expand Up @@ -654,10 +657,30 @@ class XYDraggerItem : public GuiItem
else
dragger.setCrossHair (true, true);

juce::String dotType = getProperty (pDotType);
bool matched = false;
for (int i=0; i<4; i++)
{
if (dotType == pDotTypes [i])
{
dragger.setDotType ((DOT_TYPE)i);
matched = true;
break;
}
}
if (! matched)
{
dragger.setDotType (DOT_TYPE_DOT);
}

auto radius = getProperty (pRadius);
if (! radius.isVoid())
dragger.setRadius (radius);

auto lineThickness = getProperty (pLineThickness);
if (! lineThickness.isVoid())
dragger.setLineThickness (lineThickness);

auto factor = getProperty (pSenseFactor);
if (! factor.isVoid())
dragger.setSenseFactor (factor);
Expand All @@ -676,7 +699,9 @@ class XYDraggerItem : public GuiItem
props.push_back ({ configNode, pContextParameter, SettableProperty::Choice, {}, magicBuilder.createParameterMenuLambda() });
props.push_back ({ configNode, pWheelParameter, SettableProperty::Choice, {}, magicBuilder.createParameterMenuLambda() });
props.push_back ({ configNode, pCrosshair, SettableProperty::Choice, {}, magicBuilder.createChoicesMenuLambda (pCrosshairTypes) });
props.push_back ({ configNode, pDotType, SettableProperty::Choice, {}, magicBuilder.createChoicesMenuLambda (pDotTypes) });
props.push_back ({ configNode, pRadius, SettableProperty::Number, {}, {}});
props.push_back ({ configNode, pLineThickness, SettableProperty::Number, {}, {}});
props.push_back ({ configNode, pSenseFactor, SettableProperty::Number, {}, {}});
props.push_back ({ configNode, pJumpToClick, SettableProperty::Toggle, {}, {}});

Expand All @@ -695,7 +720,10 @@ class XYDraggerItem : public GuiItem
};
const juce::Identifier XYDraggerItem::pCrosshair { "xy-crosshair" };
const juce::StringArray XYDraggerItem::pCrosshairTypes { "no-crosshair", "vertical", "horizontal", "crosshair" };
const juce::Identifier XYDraggerItem::pDotType { "xy-dot" };
const juce::StringArray XYDraggerItem::pDotTypes { "xy-dot", "xy-pole", "xy-zero", "xy-pole-zero" };
const juce::Identifier XYDraggerItem::pRadius { "xy-radius" };
const juce::Identifier XYDraggerItem::pLineThickness { "xy-line-thickness" };
const juce::Identifier XYDraggerItem::pWheelParameter { "wheel-parameter" };
const juce::Identifier XYDraggerItem::pContextParameter { "right-click" };
const juce::Identifier XYDraggerItem::pSenseFactor { "xy-sense-factor" };
Expand Down
163 changes: 163 additions & 0 deletions modules/foleys_gui_magic/Visualisers/foleys_MagicScatterPlot.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/*
==============================================================================
Copyright (c) 2019-2021 Foleys Finest Audio - Daniel Walz
All rights reserved.

License for non-commercial projects:

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.

License for commercial products:

To sell commercial products containing this module, you are required to buy a
License from https://foleysfinest.com/developer/pluginguimagic/

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
OF THE POSSIBILITY OF SUCH DAMAGE.
==============================================================================
*/

#include "foleys_MagicScatterPlot.h"

namespace foleys
{

void MagicScatterPlot::pushSamples (const juce::AudioBuffer<float>& bufferIn, int currentPlotLengthIn)
{
int numChannels = bufferIn.getNumChannels();
int chanX = std::min<int>(0,numChannels-1);
int chanY = std::min<int>(1,numChannels-1);
pushSamples(/* bufferX */ bufferIn, chanX, /* bufferY */ bufferIn, chanY, currentPlotLengthIn);
}

void MagicScatterPlot::pushSamples (const juce::AudioBuffer<float>& bufferX, int channelX,
const juce::AudioBuffer<float>& bufferY, int channelY,
int plotLengthOverride)
{
auto w = writePosition.load();

plotLengthNow = std::max<int>(0,plotLengthOverride);

const auto numSamples = bufferX.getNumSamples();
jassert(numSamples == bufferY.getNumSamples());
const auto available = samplesX.getNumSamples() - w;

const auto numChannels = bufferX.getNumChannels();
jassert(numChannels == bufferY.getNumChannels());

// plot (channelX,channelY):

if (available >= numSamples)
{
samplesX.copyFrom (0, w, bufferX.getReadPointer (channelX), numSamples);
samplesY.copyFrom (0, w, bufferY.getReadPointer (channelY), numSamples);
}
else
{
samplesX.copyFrom (0, w, bufferX.getReadPointer (channelX), available);
samplesY.copyFrom (0, w, bufferY.getReadPointer (channelY), available);
samplesX.copyFrom (0, 0, bufferX.getReadPointer (channelX, available), numSamples - available);
samplesY.copyFrom (0, 0, bufferY.getReadPointer (channelY, available), numSamples - available);
}

if (available > numSamples)
writePosition.store (w + numSamples);
else
writePosition.store (numSamples - available);

resetLastDataFlag();
}

// ====================================================================================================

void MagicScatterPlot::createPlotPaths (juce::Path& path, juce::Path& filledPath, juce::Rectangle<float> bounds, MagicAudioPlotComponent&)
{
if (sampleRate < 20.0f)
return;

const auto numToDisplay = getNumToDisplay(); // nominally plotLengthNow - defined in ./foleys_MagicAudioPlotSource.h
const auto* dataX = samplesX.getReadPointer (0);
const auto* dataY = samplesY.getReadPointer (0);

auto position = writePosition.load() - numToDisplay;
if (position < 0)
position += samplesX.getNumSamples();

if (triggeredPos || triggeredNeg) // find first zero-crossing in circular plot-buffer samplesX, giving up after 50 ms <-> 20 Hz fundamental:
{
auto positive = dataX [position] > 0.0f;
auto bail = int (sampleRate / 20.0f);

while (positive == false && --bail > 0)
{
if (--position < 0)
position += samplesX.getNumSamples();

positive = dataX [position] > 0.0f;
}

while (positive == true && --bail > 0)
{
if (--position < 0)
position += samplesX.getNumSamples();

positive = dataX [position] > 0.0f;
}
}

// FIXME: Sum channels here if X and Y are multichannel and overlay is false

path.clear();
path.startNewSubPath (juce::jmap (dataX [position], -1.0f, 1.0f, bounds.getX(), bounds.getRight()),
juce::jmap (dataY [position], -1.0f, 1.0f, bounds.getBottom(), bounds.getY()));

for (int i = 1; i < numToDisplay; ++i)
{
++position;
if (position >= samplesX.getNumSamples())
position -= samplesX.getNumSamples();

path.lineTo (juce::jmap (dataX [position], -1.0f, 1.0f, bounds.getX(), bounds.getRight()),
juce::jmap (dataY [position], -1.0f, 1.0f, bounds.getBottom(), bounds.getY()));
}

// FIXME: Make more paths here if X and Y are multichannel and overlay is true

filledPath = path;
filledPath.lineTo (bounds.getBottomRight());
filledPath.lineTo (bounds.getBottomLeft());
filledPath.closeSubPath();
}

void MagicScatterPlot::prepareToPlay (double sampleRateToUse, int)
{
sampleRate = sampleRateToUse;

samplesX.setSize (1, static_cast<int> (sampleRate));
samplesX.clear();

samplesY.setSize (1, static_cast<int> (sampleRate));
samplesY.clear();

writePosition.store (0);
}


} // namespace foleys
96 changes: 96 additions & 0 deletions modules/foleys_gui_magic/Visualisers/foleys_MagicScatterPlot.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
==============================================================================
Copyright (c) 2019-2021 Foleys Finest Audio - Daniel Walz
All rights reserved.

License for non-commercial projects:

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.

License for commercial products:

To sell commercial products containing this module, you are required to buy a
License from https://foleysfinest.com/developer/pluginguimagic/

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
OF THE POSSIBILITY OF SUCH DAMAGE.
==============================================================================
*/

#pragma once

namespace foleys
{

class MagicAudioPlotComponent;

/**
This class collects two buffers of samples in a circular buffer and
allows the GUI to draw them in the style of an scatterplot, or
XY-plot. For example, sin(t) and cos(t) produce a circle.
*/
class MagicScatterPlot : public MagicAudioPlotSource
{
public:

/**
Create an XY ScatterPlot adapter to push samples into for later display in the GUI.
*/
MagicScatterPlot () : MagicAudioPlotSource() {}

/**
Push samples to a buffer to be visualised as a scatterplot (XY plot) of channels 0 (X) and 1 (Y).
*/
void pushSamples (const juce::AudioBuffer<float>& buffer, int currentPlotLength) override;

/**
Push samples to a buffer to be visualised as a scatterplot (XY plot).

@param bufferX is plotted as the X-axis coordinate.
@param bufferY is plotted as the Y-axis coordinate.
@param plotLength, if positive, gives the preferred length of
the next plot in samples (e.g., one period).
Otherwise, 10 ms of samples is plotted.
*/
void pushSamples (const juce::AudioBuffer<float>& bufferX, int channelX,
const juce::AudioBuffer<float>& bufferY, int channelY,
const int plotLengthOverride=0) override;

/**
This is the callback that creates the frequency plot for drawing.

@param path is the path instance that is constructed by the MagicPlotSource
@param filledPath is the path instance that is constructed by the MagicPlotSource to be filled
@param bounds the bounds of the plot
@param component grants access to the plot component, e.g. to find the colours from it
*/
virtual void createPlotPaths (juce::Path& path, juce::Path& filledPath, juce::Rectangle<float> bounds, MagicAudioPlotComponent& component) override;

virtual void prepareToPlay (double sampleRate, int samplesPerBlockExpected) override;

private:

juce::AudioBuffer<float> samplesX;
juce::AudioBuffer<float> samplesY;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MagicScatterPlot)
};

} // namespace foleys
33 changes: 32 additions & 1 deletion modules/foleys_gui_magic/Widgets/foleys_XYDragComponent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,18 @@ void XYDragComponent::setCrossHair (bool horizontal, bool vertical)
wantsHorizontalDrag = vertical;
}

/**
This sets the dot type at the crosshair intersection.

@param dot plots as a filled circle
@param zero plots as an unfilled circle (O)
@param pole plots as a cross (X)
*/
void XYDragComponent::setDotType (DOT_TYPE dotTypeToDraw)
{
dotType = dotTypeToDraw;
}

void XYDragComponent::paint (juce::Graphics& g)
{
const auto x = getXposition();
Expand Down Expand Up @@ -90,14 +102,33 @@ void XYDragComponent::paint (juce::Graphics& g)
}

g.setColour (findColour (mouseOverDot ? xyDotOverColourId : xyDotColourId));
g.fillEllipse (x - radius, y - radius, 2 * radius, 2 * radius);
if (dotType == DOT_TYPE_DOT) {
g.fillEllipse (x - radius, y - radius, 2 * radius, 2 * radius);
} else if (dotType == DOT_TYPE_POLE) {
g.drawLine (x - radius, y - radius, x + radius, y + radius, lineThickness);
g.drawLine (x - radius, y + radius, x + radius, y - radius, lineThickness);
} else if (dotType == DOT_TYPE_ZERO) {
g.drawEllipse (x - radius, y - radius, 2 * radius, 2 * radius, lineThickness);
} else if (dotType == DOT_TYPE_POLE_ZERO) {
g.drawEllipse (x - radius, y - radius, 2 * radius, 2 * radius, lineThickness);
g.drawLine (x - radius, y - radius, x + radius, y + radius, lineThickness);
g.drawLine (x - radius, y + radius, x + radius, y - radius, lineThickness);
} else {
DBG("*** XYDragComponent: Invalid dotType " << dotType);
}
}

void XYDragComponent::setParameterX (juce::RangedAudioParameter* parameter)
{
xAttachment.attachToParameter (parameter);
}

void XYDragComponent::setLineThickness (float thickness)
{
lineThickness = thickness;
repaint();
}

void XYDragComponent::setParameterY (juce::RangedAudioParameter* parameter)
{
yAttachment.attachToParameter (parameter);
Expand Down
Loading