From 600f2d1251d387392b95928408868de657fc54ae Mon Sep 17 00:00:00 2001 From: xenakios Date: Tue, 27 Feb 2018 01:59:22 +0200 Subject: [PATCH] Added some new source files --- Source/envelope_component.cpp | 367 +++++++++++++++++++++ Source/envelope_component.h | 56 ++++ Source/jcdp_envelope.h | 588 ++++++++++++++++++++++++++++++++++ paulstretchplugin.jucer | 4 + 4 files changed, 1015 insertions(+) create mode 100644 Source/envelope_component.cpp create mode 100644 Source/envelope_component.h create mode 100644 Source/jcdp_envelope.h diff --git a/Source/envelope_component.cpp b/Source/envelope_component.cpp new file mode 100644 index 0000000..94ede99 --- /dev/null +++ b/Source/envelope_component.cpp @@ -0,0 +1,367 @@ +#include "envelope_component.h" + +EnvelopeComponent::EnvelopeComponent() +{ + OnEnvelopeEdited = [](breakpoint_envelope*) {}; + setWantsKeyboardFocus(true); + ValueFromNormalized = [](double x) { return x; }; + TimeFromNormalized = [](double x) { return x; }; + addChildComponent(&m_bubble); + setOpaque(true); +} + +EnvelopeComponent::~EnvelopeComponent() +{ + +} + +void EnvelopeComponent::show_bubble(int x, int y, const envelope_node& node) +{ + double scaledtime = TimeFromNormalized(node.Time); + double scaledvalue = ValueFromNormalized(node.Value); + x -= 50; + if (x < 0) + x = 0; + if (x + 100 > getWidth()) + x = getWidth() - 100; + if (y < 0) + y = 0; + if (y + 20 > getHeight()) + y = getHeight() - 20; + AttributedString temp(String::formatted("%.2f %.2f", scaledtime, scaledvalue)); + temp.setColour(Colours::white); + m_bubble.showAt({ x,y,100,20 }, temp , 5000); +} + + +void EnvelopeComponent::paint(Graphics& g) +{ + if (!EnvelopeUnderlayDraw) + { + g.fillAll(Colours::black); + g.setColour(Colours::white.darker()); + juce::Rectangle rect(0, 0, getWidth(), getHeight()); + + g.setFont(15.0); + + } + else + { + g.saveState(); + EnvelopeUnderlayDraw(this, g); + g.restoreState(); + } + + if (m_envelope == nullptr) + { + g.drawText("No envelope set", 10, 10, getWidth(), getHeight(), Justification::centred); + return; + } + if (m_envelope.unique() == true) + { + g.drawText("Envelope is orphaned (may be a bug)", 10, 10, getWidth(), getHeight(), Justification::centred); + return; + } + String name = m_name; + if (name.isEmpty() == true) + name = "Untitled envelope"; + g.drawText(name, 10, 10, getWidth(), getHeight(), Justification::topLeft); + const float linethickness = 1.0f; + for (int i = 0; i < m_envelope->GetNumNodes(); ++i) + { + const envelope_node& pt = m_envelope->GetNodeAtIndex(i); + double xcor = jmap(pt.Time, m_view_start_time, m_view_end_time, 0.0, (double)getWidth()); + double ycor = (double)getHeight()-jmap(pt.Value, m_view_start_value, m_view_end_value, 0.0,(double)getHeight()); + g.setColour(Colours::white); + if (pt.Status==0) + g.drawRect((float)xcor - 4.0f, (float)ycor - 4.0f, 8.0f, 8.0f, 1.0f); + else g.fillRect((float)xcor - 4.0f, (float)ycor - 4.0f, 8.0f, 8.0f); + m_envelope->resamplePointToLinearSegments(i,0.0, 1.0, 0.0, 1.0, [&g,linethickness,this](double pt_x0, double pt_y0, double pt_x1, double pt_y1) + { + double foo_x0 = jmap(pt_x0, m_view_start_time, m_view_end_time, 0.0, getWidth()); + double foo_x1 = jmap(pt_x1, m_view_start_time, m_view_end_time, 0.0, getWidth()); + double foo_y0 = (double)getHeight() - jmap(pt_y0, m_view_start_value, m_view_end_value, 0.0, getHeight()); + double foo_y1 = (double)getHeight() - jmap(pt_y1, m_view_start_value, m_view_end_value, 0.0, getHeight()); + g.setColour(m_env_color); + g.drawLine((float)foo_x0, (float)foo_y0, (float)foo_x1, (float)foo_y1, linethickness); + //g.setColour(Colours::white); + //g.drawLine(foo_x0, foo_y0 - 8.0, foo_x0, foo_y0 + 8.0, 1.0); + }, [this](double xdiff) + { + return std::max((int)(xdiff*getWidth() / 16), 8); + }); + + /* + envelope_node pt1; + if (i + 1 < m_envelope->GetNumNodes()) + { + g.setColour(m_env_color); + pt1 = m_envelope->GetNodeAtIndex(i + 1); + double xcor1 = jmap(pt1.Time, m_view_start_time, m_view_end_time, 0.0, (double)getWidth()); + double ycor1 = (double)getHeight() - jmap(pt1.Value, m_view_start_value, m_view_end_value, 0.0, (double)getHeight()); + g.drawLine((float)xcor, (float)ycor, (float)xcor1, (float)ycor1, linethickness); + } + if (i == 0 && pt.Time >= 0.0) + { + g.setColour(m_env_color); + g.drawLine(0.0f, (float)ycor, (float)xcor, float(ycor), linethickness); + } + if (i == m_envelope->GetNumNodes()-1 && pt.Time < 1.0) + { + g.setColour(m_env_color); + g.drawLine((float)xcor, (float)ycor, (float)getWidth(), float(ycor), linethickness); + } + */ + } +#ifdef ENVELOPEDRAWDERIVATIVE + g.setColour(Colours::green); + //double prevderiv = derivative([this](double xx) { return m_envelope->GetInterpolatedNodeValue(xx); }, 0.0); + for (int i = 0; i < getWidth()/8; ++i) + { + double x = 1.0 / getWidth()*(i*8); + double derv = derivative([this](double xx) { return m_envelope->GetInterpolatedNodeValue(xx); }, x); + double y = getHeight()-jmap(derv, -10.0, 10.0, 0.0, getHeight()); + g.fillEllipse(i*8, y, 5.0f, 5.0f); + } +#endif +} + +void EnvelopeComponent::changeListenerCallback(ChangeBroadcaster*) +{ + repaint(); +} + +void EnvelopeComponent::timerCallback(int) +{ + +} + +void EnvelopeComponent::set_envelope(std::shared_ptr env, String name) +{ + m_envelope = env; + m_name = name; + repaint(); +} + +void EnvelopeComponent::mouseDrag(const MouseEvent& ev) +{ + if (m_envelope == nullptr) + return; + if (m_segment_drag_info.first >= 0 && ev.mods.isAltDown()) + { + double dist = jmap(ev.getDistanceFromDragStartX(), -300.0, 300.0, -1.0, 1.0); + m_envelope->performRelativeTransformation([dist, this](int index, envelope_node& point) + { + if (index == m_segment_drag_info.first) + { + point.ShapeParam1 += dist; + m_segment_drag_info.second = true; + } + }); + repaint(); + return; + } + if (m_segment_drag_info.first >= 0) + { + double dist = jmap(ev.getDistanceFromDragStartY(), -getHeight(), getHeight(), -1.0, 1.0); + m_envelope->adjustEnvelopeSegmentValues(m_segment_drag_info.first, -dist); + repaint(); + return; + } + if (m_node_to_drag >= 0) + { + //Logger::writeToLog("trying to move pt " + String(m_node_to_drag)); + envelope_node& pt = m_envelope->GetNodeAtIndex(m_node_to_drag); + double left_bound = m_view_start_time; + double right_bound = m_view_end_time; + if (m_node_to_drag > 0 ) + { + left_bound = m_envelope->GetNodeAtIndex(m_node_to_drag - 1).Time; + } + if (m_node_to_drag < m_envelope->GetNumNodes() - 1) + { + right_bound = m_envelope->GetNodeAtIndex(m_node_to_drag + 1).Time; + } + double normx = jmap((double)ev.x, 0.0, (double)getWidth(), m_view_start_time, m_view_end_time); + double normy = jmap((double)getHeight() - ev.y, 0.0, (double)getHeight(), m_view_start_value, m_view_end_value); + pt.Time=jlimit(left_bound+0.001, right_bound - 0.001, normx); + pt.Value=jlimit(0.0,1.0,normy); + m_last_tip = String(pt.Time, 2) + " " + String(pt.Value, 2); + show_bubble(ev.x, ev.y, pt); + m_node_that_was_dragged = m_node_to_drag; + repaint(); + return; + } +} + +void EnvelopeComponent::mouseMove(const MouseEvent & ev) +{ + if (m_envelope == nullptr) + return; + m_node_to_drag = find_hot_envelope_point(ev.x, ev.y); + if (m_node_to_drag >= 0) + { + if (m_mouse_down == false) + { + show_bubble(ev.x, ev.y, m_envelope->GetNodeAtIndex(m_node_to_drag)); + setMouseCursor(MouseCursor::PointingHandCursor); + } + } + else + { + setMouseCursor(MouseCursor::NormalCursor); + m_bubble.setVisible(false); + } +} + +void EnvelopeComponent::mouseDown(const MouseEvent & ev) +{ + if (m_envelope == nullptr) + return; + if (ev.mods.isRightButtonDown() == true) + { + PopupMenu menu; + menu.addItem(1, "Reset"); + menu.addItem(2, "Invert"); + int r = menu.show(); + if (r == 1) + { + m_envelope->ResetEnvelope(); + } + if (r == 2) + { + for (int i = 0; i < m_envelope->GetNumNodes(); ++i) + { + double val = 1.0 - m_envelope->GetNodeAtIndex(i).Value; + m_envelope->GetNodeAtIndex(i).Value = val; + } + } + repaint(); + return; + } + m_node_to_drag = find_hot_envelope_point(ev.x, ev.y); + m_mouse_down = true; + m_segment_drag_info = { findHotEnvelopeSegment(ev.x, ev.y, true),false }; + if (m_segment_drag_info.first >= 0) + { + m_envelope->beginRelativeTransformation(); + return; + } + if (m_node_to_drag >= 0 && ev.mods.isAltDown() == true) + { + if (m_envelope->GetNumNodes() < 2) + { + m_bubble.showAt({ ev.x,ev.y, 0,0 }, AttributedString("Can't remove last node"), 3000, false, false); + return; + } + m_envelope->DeleteNode(m_node_to_drag); + m_node_to_drag = -1; + OnEnvelopeEdited(m_envelope.get()); + repaint(); + return; + } + if (m_node_to_drag >= 0 && ev.mods.isShiftDown()==true) + { + int oldstatus = m_envelope->GetNodeAtIndex(m_node_to_drag).Status; + if (oldstatus==0) + m_envelope->GetNodeAtIndex(m_node_to_drag).Status=1; + else m_envelope->GetNodeAtIndex(m_node_to_drag).Status=0; + repaint(); + return; + } + if (m_node_to_drag == -1) + { + double normx = jmap((double)ev.x, 0.0, (double)getWidth(), m_view_start_time, m_view_end_time); + double normy = jmap((double)getHeight() - ev.y, 0.0, (double)getHeight(), m_view_start_value, m_view_end_value); + m_envelope->AddNode ({ normx,normy, 0.5}); + m_envelope->SortNodes(); + m_mouse_down = false; + OnEnvelopeEdited(m_envelope.get()); + repaint(); + } +} + +void EnvelopeComponent::mouseUp(const MouseEvent &ev) +{ + if (ev.mods == ModifierKeys::noModifiers) + m_bubble.setVisible(false); + if (m_node_that_was_dragged >= 0 || m_segment_drag_info.second==true) + { + OnEnvelopeEdited(m_envelope.get()); + } + m_mouse_down = false; + m_node_that_was_dragged = -1; + m_node_to_drag = -1; + if (m_segment_drag_info.second == true) + { + m_segment_drag_info = { -1,false }; + m_envelope->endRelativeTransformation(); + } +} + +bool EnvelopeComponent::keyPressed(const KeyPress & ev) +{ + if (ev == KeyPress::deleteKey && m_envelope!=nullptr) + { + m_node_to_drag = -1; + //m_envelope->ClearAllNodes(); + m_envelope->removePointsConditionally([](const envelope_node& pt) { return pt.Status == 1; }); + if (m_envelope->GetNumNodes()==0) + m_envelope->AddNode({ 0.0,0.5 }); + repaint(); + OnEnvelopeEdited(m_envelope.get()); + + return true; + } + return false; +} + +int EnvelopeComponent::find_hot_envelope_point(double xcor, double ycor) +{ + if (m_envelope == nullptr) + return -1; + for (int i = 0; i < m_envelope->GetNumNodes(); ++i) + { + const envelope_node& pt = m_envelope->GetNodeAtIndex(i); + double ptxcor = jmap(pt.Time, m_view_start_time, m_view_end_time, 0.0, (double)getWidth()); + double ptycor = (double)getHeight() - jmap(pt.Value, m_view_start_value, m_view_end_value, 0.0, (double)getHeight()); + juce::Rectangle target(ptxcor - 4.0, ptycor - 4.0, 8.0, 8.0); + if (target.contains(xcor, ycor) == true) + { + return i; + } + } + return -1; +} + +int EnvelopeComponent::findHotEnvelopeSegment(double xcor, double ycor, bool detectsegment) +{ + if (m_envelope == nullptr) + return -1; + for (int i = 0; i < m_envelope->GetNumNodes()-1; ++i) + { + const envelope_node& pt0 = m_envelope->GetNodeAtIndex(i); + const envelope_node& pt1 = m_envelope->GetNodeAtIndex(i+1); + float xcor0 = (float)jmap(pt0.Time, m_view_start_time, m_view_end_time, 0.0, getWidth()); + float xcor1 = (float)jmap(pt1.Time, m_view_start_time, m_view_end_time, 0.0, getWidth()); + float segwidth = xcor1 - xcor0; + juce::Rectangle segrect(xcor0+8.0f, 0.0f, segwidth-16.0f, (float)getHeight()); + if (segrect.contains((float)xcor, (float)ycor)) + { + if (detectsegment == false) + return i; + else + { + double normx = jmap(xcor, 0.0, getWidth(), m_view_start_time, m_view_end_time); + double yval = m_envelope->GetInterpolatedNodeValue(normx); + float ycor0 = (float)(getHeight()-jmap(yval, 0.0, 1.0, 0.0, getHeight())); + juce::Rectangle segrect2((float)(xcor - 20), (float)(ycor - 20), 40, 40); + if (segrect2.contains((float)xcor, ycor0)) + return i; + } + } + + + } + return -1; +} diff --git a/Source/envelope_component.h b/Source/envelope_component.h new file mode 100644 index 0000000..2219bd6 --- /dev/null +++ b/Source/envelope_component.h @@ -0,0 +1,56 @@ +#pragma once + +#include "../JuceLibraryCode/JuceHeader.h" +#include +#include +#include +#include "jcdp_envelope.h" + +class EnvelopeComponent : public Component, + public ChangeListener, + public MultiTimer + //public TooltipClient +{ +public: + EnvelopeComponent(); + ~EnvelopeComponent(); + void paint(Graphics& g) override; + void set_envelope(std::shared_ptr env, String name = String()); + std::shared_ptr get_envelope() { return m_envelope; } + String get_name() { return m_name; } + void mouseMove(const MouseEvent& ev) override; + void mouseDown(const MouseEvent& ev) override; + void mouseDrag(const MouseEvent& ev) override; + void mouseUp(const MouseEvent& ev) override; + bool keyPressed(const KeyPress& ev) override; + double get_view_start_time() const { return m_view_start_time; } + double get_view_end_time() const { return m_view_end_time; } + void set_view_start_time(double t) { m_view_start_time = t; repaint(); } + void set_view_end_time(double t) { m_view_end_time = t; repaint(); } + std::function EnvelopeUnderlayDraw; + std::function EnvelopeOverlayDraw; + std::function OnEnvelopeEdited; + std::function ValueFromNormalized; + std::function TimeFromNormalized; + void changeListenerCallback(ChangeBroadcaster*) override; + void timerCallback(int id) override; + //String getTooltip() override; +private: + std::shared_ptr m_envelope; + String m_name; + Colour m_env_color{ Colours::yellow }; + double m_view_start_time = 0.0; + double m_view_end_time = 1.0; + double m_view_start_value = 0.0; + double m_view_end_value = 1.0; + int find_hot_envelope_point(double xcor, double ycor); + int findHotEnvelopeSegment(double xcor, double ycor, bool detectsegment); + bool m_mouse_down = false; + int m_node_to_drag = -1; + std::pair m_segment_drag_info{ -1,false }; + int m_node_that_was_dragged = -1; + String m_last_tip; + BubbleMessageComponent m_bubble; + void show_bubble(int x, int y, const envelope_node &node); + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(EnvelopeComponent) +}; diff --git a/Source/jcdp_envelope.h b/Source/jcdp_envelope.h new file mode 100644 index 0000000..454db0a --- /dev/null +++ b/Source/jcdp_envelope.h @@ -0,0 +1,588 @@ +/* +This file is part of CDP Front-end. + +CDP front-end is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +CDP front-end is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with CDP front-end. If not, see . +*/ + +#ifndef JCDP_ENVELOPE_H +#define JCDP_ENVELOPE_H + +#include +#include +#include "../JuceLibraryCode/JuceHeader.h" +#include "PS_Source/globals.h" + +struct envelope_node +{ + envelope_node() + : Time(0.0), Value(0.0), ShapeParam1(0.5), ShapeParam2(0.5) {} + envelope_node(double x, double y, double p1=0.5, double p2=0.5) + : Time(x), Value(y),ShapeParam1(p1),ShapeParam2(p2) {} + double Time; + double Value; + int Shape = 0; + double ShapeParam1; + double ShapeParam2; + int Status = 0; + size_t get_hash() const + { + size_t seed = 0; + seed ^= std::hash()(Time) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + seed ^= std::hash()(Value) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + seed ^= std::hash()(Shape) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + seed ^= std::hash()(ShapeParam1) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + seed ^= std::hash()(ShapeParam2) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + return seed; + } + + +}; + +inline bool operator<(const envelope_node& a, const envelope_node& b) +{ + return a.Time +inline void appendToMemoryBlock(MemoryBlock& mb, T x) +{ + T temp(x); + mb.append((void*)&temp, sizeof(temp)); +} + + +struct grid_entry +{ + grid_entry(double v) : m_value(v) {} + double m_value=0.0; + bool m_foo=false; +}; + +inline double grid_value(const grid_entry& ge) +{ + return ge.m_value; +} + +inline bool operator<(const grid_entry& a, const grid_entry& b) +{ + return a.m_value; + +//#define BEZIER_EXPERIMENT + +inline double get_shaped_value(double x, int, double p1, double) +{ +#ifndef BEZIER_EXPERIMENT + if (p1<0.5) + { + double foo=1.0-(p1*2.0); + return 1.0-pow(1.0-x,1.0+foo*4.0); + } + double foo=(p1-0.5)*2.0; + return pow(x,1.0+foo*4.0); +#else + /* + double pt0=-2.0*p1; + double pt1=2.0*p2; + double pt2=1.0; + return pow(1-x,2.0)*pt0+2*(1-x)*x*pt1+pow(x,2)*pt2; + */ + if (p2<=0.5) + { + if (p1<0.5) + { + double foo=1.0-(p1*2.0); + return 1.0-pow(1.0-x,1.0+foo*4.0); + } + double foo=(p1-0.5)*2.0; + return pow(x,1.0+foo*4.0); + } else + { + if (p1<0.5) + { + if (x<0.5) + { + x*=2.0; + p1*=2.0; + return 0.5*pow(x,p1*4.0); + } else + { + x-=0.5; + x*=2.0; + p1*=2.0; + return 1.0-0.5*pow(1.0-x,p1*4.0); + } + } else + { + if (x<0.5) + { + x*=2.0; + p1-=0.5; + p1*=2.0; + return 0.5-0.5*pow(1.0-x,p1*4.0); + } else + { + x-=0.5; + x*=2.0; + p1-=0.5; + p1*=2.0; + return 0.5+0.5*pow(x,p1*4.0); + } + } + } + return x; +#endif +} + +using nodes_t=std::vector; + +inline double GetInterpolatedNodeValue(const nodes_t& m_nodes, double atime, double m_defvalue=0.5) +{ + int maxnodeind=(int)m_nodes.size()-1; + if (m_nodes.size()==0) return m_defvalue; + if (m_nodes.size()==1) return m_nodes[0].Value; + if (atime<=m_nodes[0].Time) + return m_nodes[0].Value; + if (atime>m_nodes[maxnodeind].Time) + return m_nodes[maxnodeind].Value; + const envelope_node to_search(atime,0.0); + //to_search.Time=atime; + auto it=std::lower_bound(m_nodes.begin(),m_nodes.end(),to_search, + [](const envelope_node& a, const envelope_node& b) + { return a.TimeTime; + double v1=it->Value; + double p1=it->ShapeParam1; + double p2=it->ShapeParam2; + ++it; // next envelope point + double tdelta=it->Time-t1; + if (tdelta<0.00001) + tdelta=0.00001; + double vdelta=it->Value-v1; + return v1+vdelta*get_shaped_value(((1.0/tdelta*(atime-t1))),0,p1,p2); + +} + +inline double interpolate_foo(double atime,double t0, double v0, double t1, double v1, double p1, double p2) +{ + double tdelta=t1-t0; + if (tdelta<0.00001) + tdelta=0.00001; + double vdelta=v1-v0; + return v0+vdelta*get_shaped_value(((1.0/tdelta*(atime-t0))),0,p1,p2); +} + +class breakpoint_envelope +{ +public: + breakpoint_envelope() : m_name("Untitled") {} + breakpoint_envelope(String name, double minv=0.0, double maxv=1.0) + : m_minvalue(minv), m_maxvalue(maxv), m_name(name) + { + m_defshape=0; + //m_color=RGB(0,255,255); + m_defvalue=0.5; + m_updateopinprogress=false; + m_value_grid={0.0,0.25,0.5,0.75,1.0}; + } + + + void SetName(String Name) { m_name=Name; } + const String& GetName() const { return m_name; } + double GetDefValue() { return m_defvalue; } + void SetDefValue(double value) { m_defvalue=value; } + int GetDefShape() { return m_defshape; } + ValueTree saveState() + { + ValueTree result("envelope"); + for (int i = 0; i < m_nodes.size(); ++i) + { + ValueTree pt_tree("pt"); + storeToTreeProperties(pt_tree, nullptr, + "x", m_nodes[i].Time, "y", m_nodes[i].Value, "p1", m_nodes[i].ShapeParam1, "p2", m_nodes[i].ShapeParam2); + result.addChild(pt_tree, -1, nullptr); + } + return result; + } + void restoreState(ValueTree state) + { + int numnodes = state.getNumChildren(); + if (numnodes > 0) + { + m_nodes.clear(); + for (int i = 0; i < numnodes; ++i) + { + ValueTree pt_tree = state.getChild(i); + double x, y = 0.0; + double p1, p2 = 0.5; + getFromTreeProperties(pt_tree, "x", x, "y", y, "p1", p1, "p2", p2); + m_nodes.emplace_back(x, y, p1,p2); + } + SortNodes(); + } + } + MD5 getHash() const + { + MemoryBlock mb; + for (int i = 0; i < m_nodes.size(); ++i) + { + appendToMemoryBlock(mb, m_nodes[i].Time); + appendToMemoryBlock(mb, m_nodes[i].Value); + appendToMemoryBlock(mb, m_nodes[i].ShapeParam1); + appendToMemoryBlock(mb, m_nodes[i].ShapeParam2); + } + return MD5(mb); + } + + int GetNumNodes() const { return (int)m_nodes.size(); } + void SetDefShape(int value) { m_defshape=value; } + double getNodeLeftBound(int index, double margin=0.01) const noexcept + { + if (m_nodes.size() == 0) + return 0.0; + if (index == 0) + return 0.0; + return m_nodes[index - 1].Time + margin; + } + double getNodeRightBound(int index, double margin = 0.01) const noexcept + { + if (m_nodes.size() == 0) + return 1.0; + if (index == m_nodes.size()-1) + return 1.0; + return m_nodes[index + 1].Time - margin; + } + const std::vector& get_all_nodes() const { return m_nodes; } + void set_all_nodes(nodes_t nds) { m_nodes=std::move(nds); } + void set_reset_nodes(const std::vector& nodes, bool convertvalues=false) + { + if (convertvalues==false) + m_reset_nodes=nodes; + else + { + if (scaled_to_normalized_func) + { + m_nodes.clear(); + for (int i=0;im_nodes.size()-1) + return; + m_nodes.erase(m_nodes.begin()+indx); + } + void delete_nodes_in_time_range(double t0, double t1) + { + m_nodes.erase(std::remove_if(std::begin(m_nodes), + std::end(m_nodes), + [t0,t1](const envelope_node& a) { return a.Time>=t0 && a.Time<=t1; } ), + std::end(m_nodes) ); + } + template + void removePointsConditionally(F predicate) + { + m_nodes.erase(std::remove_if(m_nodes.begin(), m_nodes.end(), predicate), m_nodes.end()); + } + envelope_node& GetNodeAtIndex(int indx) + { + if (m_nodes.size()==0) + { + throw(std::length_error("Empty envelope accessed")); + } + if (indx<0) + indx=0; + if (indx>=(int)m_nodes.size()) + indx=(int)m_nodes.size()-1; + return m_nodes[indx]; + } + const envelope_node& GetNodeAtIndex(int indx) const + { + if (m_nodes.size()==0) + { + throw(std::length_error("Empty envelope accessed")); + } + if (indx<0) + indx=0; + if (indx>=(int)m_nodes.size()) + indx=(int)m_nodes.size()-1; + return m_nodes[indx]; + } + void SetNodeStatus(int indx, int nstatus) + { + int i=indx; + if (indx<0) i=0; + if (indx>(int)m_nodes.size()-1) i=(int)m_nodes.size()-1; + m_nodes[i].Status=nstatus; + } + void SetNode(int indx, envelope_node anode) + { + int i=indx; + if (indx<0) i=0; + if (indx>(int)m_nodes.size()-1) i=(int)m_nodes.size()-1; + m_nodes[i]=anode; + } + void SetNodeTimeValue(int indx,bool setTime,bool setValue,double atime,double avalue) + { + int i=indx; + if (indx<0) i=0; + if (indx>(int)m_nodes.size()-1) i=(int)m_nodes.size()-1; + if (setTime) m_nodes[i].Time=atime; + if (setValue) m_nodes[i].Value=avalue; + } + + + double GetInterpolatedNodeValue(double atime) + { + double t0=0.0; + double t1=0.0; + double v0=0.0; + double v1=0.0; + double p1=0.0; + double p2=0.0; + const int maxnodeind=(int)m_nodes.size()-1; + if (m_nodes.size()==0) + return m_defvalue; + if (m_nodes.size()==1) + return m_nodes[0].Value; + if (atime<=m_nodes[0].Time) + { +#ifdef INTERPOLATING_ENVELOPE_BORDERS + t1=m_nodes[0].Time; + t0=0.0-(1.0-m_nodes[maxnodeind].Time); + v0=m_nodes[maxnodeind].Value; + p1=m_nodes[maxnodeind].ShapeParam1; + p2=m_nodes[maxnodeind].ShapeParam2; + v1=m_nodes[0].Value; + return interpolate_foo(atime,t0,v0,t1,v1,p1,p2); +#else + return m_nodes[0].Value; +#endif + } + if (atime>m_nodes[maxnodeind].Time) + { +#ifdef INTERPOLATING_ENVELOPE_BORDERS + t0=m_nodes[maxnodeind].Time; + t1=1.0+(m_nodes[0].Time); + v0=m_nodes[maxnodeind].Value; + v1=m_nodes[0].Value; + p1=m_nodes[maxnodeind].ShapeParam1; + p2=m_nodes[maxnodeind].ShapeParam2; + return interpolate_foo(atime,t0,v0,t1,v1,p1,p2); +#else + return m_nodes.back().Value; +#endif + } + const envelope_node to_search(atime,0.0); + //to_search.Time=atime; + auto it=std::lower_bound(m_nodes.begin(),m_nodes.end(),to_search, + [](const envelope_node& a, const envelope_node& b) + { return a.TimeTime; + v0=it->Value; + p1=it->ShapeParam1; + p2=it->ShapeParam2; + ++it; // next envelope point + t1=it->Time; + v1=it->Value; + return interpolate_foo(atime,t0,v0,t1,v1,p1,p2); + } + bool IsSorted() const + { + return std::is_sorted(m_nodes.begin(), m_nodes.end(), [] + (const envelope_node& lhs, const envelope_node& rhs) + { + return lhs.Time normalized_to_scaled_func; + std::function scaled_to_normalized_func; + void beginRelativeTransformation() + { + m_old_nodes=m_nodes; + } + void endRelativeTransformation() + { + m_old_nodes.clear(); + } + nodes_t& getRelativeTransformBaseNodes() + { + return m_old_nodes; + } + template + inline void performRelativeTransformation(F&& f) + { + for (int i = 0; i < m_old_nodes.size(); ++i) + { + envelope_node node = m_old_nodes[i]; + f(i, node); + node.ShapeParam1 = jlimit(0.0, 1.0, node.ShapeParam1); + m_nodes[i] = node; + } + } + void adjustEnvelopeSegmentValues(int index, double amount) + { + if (index >= m_old_nodes.size()) + { + m_nodes.back().Value = jlimit(0.0,1.0,m_old_nodes.back().Value+amount); + return; + } + m_nodes[index].Value = jlimit(0.0, 1.0, m_old_nodes[index].Value + amount); + m_nodes[index+1].Value = jlimit(0.0, 1.0, m_old_nodes[index+1].Value + amount); + } + const nodes_t& repeater_nodes() const + { + return m_repeater_nodes; + } + void store_repeater_nodes() + { + m_repeater_nodes.clear(); + for (int i=0;i=m_playoffset && m_nodes[i].Time<=m_playoffset+1.0) + { + envelope_node temp=m_nodes[i]; + temp.Time-=m_playoffset; + m_repeater_nodes.push_back(temp); + } + } + } + double get_play_offset() const { return m_playoffset; } + //void set_play_offset(double x) { m_playoffset=bound_value(m_mintime,x,m_maxtime); } + //time_range get_play_offset_range() const { return std::make_pair(m_mintime,m_maxtime); } + const grid_t& get_value_grid() const { return m_value_grid; } + void set_value_grid(grid_t g) { m_value_grid=std::move(g); } + template + void manipulate(F&& f) + { + nodes_t backup=m_nodes; + if (f(backup)==true) + { + std::swap(backup,m_nodes); + SortNodes(); + } + } + template + inline void resamplePointToLinearSegments(int point_index,double /*xmin*/, double /*xmax*/, double /*ymin*/, double /*ymax*/, + F0&& handlesegmentfunc, F1&& numsegmentsfunc) + { + if (m_nodes.size() == 0) + return; + + envelope_node pt0 = GetNodeAtIndex(point_index); + envelope_node pt1 = GetNodeAtIndex(point_index+1); + double xdiff = pt1.Time - pt0.Time; + if (xdiff > 0.0) + { + int numsegments = numsegmentsfunc(xdiff); + for (int j=0;j +inline double derivative(const F& f, double x, const Args&... func_args) +{ + const double epsilon = std::numeric_limits::epsilon() * 100; + //const double epsilon=0.000001; + return (f(x + epsilon, func_args...) - f(x, func_args...)) / epsilon; +} + + +#endif // JCDP_ENVELOPE_H diff --git a/paulstretchplugin.jucer b/paulstretchplugin.jucer index c85f379..f5b5f53 100644 --- a/paulstretchplugin.jucer +++ b/paulstretchplugin.jucer @@ -15,6 +15,10 @@ headerPath=" "> + +