Add curves
This commit is contained in:
parent
d12aaa9f48
commit
357b17d55f
10 changed files with 662 additions and 2 deletions
|
|
@ -50,6 +50,7 @@ else()
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
||||||
# If we build with Clang use libc++
|
# If we build with Clang use libc++
|
||||||
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND NOT ANDROID)
|
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND NOT ANDROID)
|
||||||
set(USE_LIBCPP ON CACHE BOOL "Use libc++ with clang")
|
set(USE_LIBCPP ON CACHE BOOL "Use libc++ with clang")
|
||||||
|
|
@ -64,6 +65,9 @@ endif()
|
||||||
add_library(sfizz-pugixml STATIC "src/external/pugixml/src/pugixml.cpp")
|
add_library(sfizz-pugixml STATIC "src/external/pugixml/src/pugixml.cpp")
|
||||||
target_include_directories(sfizz-pugixml PUBLIC "src/external/pugixml/src")
|
target_include_directories(sfizz-pugixml PUBLIC "src/external/pugixml/src")
|
||||||
|
|
||||||
|
add_library(sfizz-spline STATIC "src/external/spline/spline/spline.cpp")
|
||||||
|
target_include_directories(sfizz-spline PUBLIC "src/external/spline")
|
||||||
|
|
||||||
include (CheckLibraryExists)
|
include (CheckLibraryExists)
|
||||||
if (UNIX AND NOT APPLE)
|
if (UNIX AND NOT APPLE)
|
||||||
check_library_exists(atomic __atomic_load "" LIBATOMIC_FOUND)
|
check_library_exists(atomic __atomic_load "" LIBATOMIC_FOUND)
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ set (SFIZZ_SOURCES
|
||||||
sfizz/FloatEnvelopes.cpp
|
sfizz/FloatEnvelopes.cpp
|
||||||
sfizz/Logger.cpp
|
sfizz/Logger.cpp
|
||||||
sfizz/SfzFilter.cpp
|
sfizz/SfzFilter.cpp
|
||||||
|
sfizz/Curve.cpp
|
||||||
sfizz/Effects.cpp
|
sfizz/Effects.cpp
|
||||||
sfizz/effects/Nothing.cpp
|
sfizz/effects/Nothing.cpp
|
||||||
sfizz/effects/Filter.cpp
|
sfizz/effects/Filter.cpp
|
||||||
|
|
@ -36,7 +37,7 @@ target_sources(sfizz_static PRIVATE ${SFIZZ_SOURCES} sfizz/sfizz_wrapper.cpp sfi
|
||||||
target_include_directories (sfizz_static PUBLIC .)
|
target_include_directories (sfizz_static PUBLIC .)
|
||||||
target_include_directories (sfizz_static PUBLIC external)
|
target_include_directories (sfizz_static PUBLIC external)
|
||||||
target_link_libraries (sfizz_static PUBLIC absl::strings absl::span)
|
target_link_libraries (sfizz_static PUBLIC absl::strings absl::span)
|
||||||
target_link_libraries (sfizz_static PRIVATE sfizz_parser absl::flat_hash_map Threads::Threads sfizz-sndfile sfizz-pugixml)
|
target_link_libraries (sfizz_static PRIVATE sfizz_parser absl::flat_hash_map Threads::Threads sfizz-sndfile sfizz-pugixml sfizz-spline)
|
||||||
set_target_properties (sfizz_static PROPERTIES OUTPUT_NAME sfizz PUBLIC_HEADER "sfizz.h;sfizz.hpp")
|
set_target_properties (sfizz_static PROPERTIES OUTPUT_NAME sfizz PUBLIC_HEADER "sfizz.h;sfizz.hpp")
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
target_compile_definitions (sfizz_static PRIVATE _USE_MATH_DEFINES)
|
target_compile_definitions (sfizz_static PRIVATE _USE_MATH_DEFINES)
|
||||||
|
|
@ -69,7 +70,7 @@ if (SFIZZ_SHARED)
|
||||||
target_sources(sfizz_shared PRIVATE ${SFIZZ_SOURCES} sfizz/sfizz_wrapper.cpp sfizz/sfizz.cpp)
|
target_sources(sfizz_shared PRIVATE ${SFIZZ_SOURCES} sfizz/sfizz_wrapper.cpp sfizz/sfizz.cpp)
|
||||||
target_include_directories (sfizz_shared PRIVATE .)
|
target_include_directories (sfizz_shared PRIVATE .)
|
||||||
target_include_directories (sfizz_shared PRIVATE external)
|
target_include_directories (sfizz_shared PRIVATE external)
|
||||||
target_link_libraries (sfizz_shared PRIVATE absl::strings absl::span sfizz_parser absl::flat_hash_map Threads::Threads sfizz-sndfile sfizz-pugixml)
|
target_link_libraries (sfizz_shared PRIVATE absl::strings absl::span sfizz_parser absl::flat_hash_map Threads::Threads sfizz-sndfile sfizz-pugixml sfizz-spline)
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
target_compile_definitions (sfizz_shared PRIVATE _USE_MATH_DEFINES)
|
target_compile_definitions (sfizz_shared PRIVATE _USE_MATH_DEFINES)
|
||||||
endif()
|
endif()
|
||||||
|
|
|
||||||
29
src/external/spline/LICENSE
vendored
Normal file
29
src/external/spline/LICENSE
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
BSD 3-Clause License
|
||||||
|
|
||||||
|
Copyright (c) 2018, Roland Rabien
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* 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.
|
||||||
|
|
||||||
|
* 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.
|
||||||
|
|
||||||
|
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.
|
||||||
77
src/external/spline/spline/spline.cpp
vendored
Normal file
77
src/external/spline/spline/spline.cpp
vendored
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
/*==============================================================================
|
||||||
|
|
||||||
|
Copyright 2018 by Roland Rabien, Devin Lane
|
||||||
|
For more information visit www.rabiensoftware.com
|
||||||
|
|
||||||
|
==============================================================================*/
|
||||||
|
|
||||||
|
/* "THE BEER-WARE LICENSE" (Revision 42): Devin Lane wrote this file. As long as you retain
|
||||||
|
* this notice you can do whatever you want with this stuff. If we meet some day, and you
|
||||||
|
* think this stuff is worth it, you can buy me a beer in return. */
|
||||||
|
|
||||||
|
#include "spline.h"
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
Spline::Spline (const double *pointsX, const double *pointsY, int numPoints)
|
||||||
|
{
|
||||||
|
assert (numPoints >= 3); // "Must have at least three points for interpolation"
|
||||||
|
|
||||||
|
int n = numPoints - 1;
|
||||||
|
|
||||||
|
std::vector<double> b, d, a, c, l, u, z, h;
|
||||||
|
|
||||||
|
a.resize (n);
|
||||||
|
b.resize (n);
|
||||||
|
c.resize (n + 1);
|
||||||
|
d.resize (n);
|
||||||
|
h.resize (n + 1);
|
||||||
|
l.resize (n + 1);
|
||||||
|
u.resize (n + 1);
|
||||||
|
z.resize (n + 1);
|
||||||
|
|
||||||
|
l[0] = 1.0;
|
||||||
|
u[0] = 0.0;
|
||||||
|
z[0] = 0.0;
|
||||||
|
h[0] = pointsX[1] - pointsX[0];
|
||||||
|
|
||||||
|
for (int i = 1; i < n; i++)
|
||||||
|
{
|
||||||
|
h[i] = pointsX[i+1] - pointsX[i];
|
||||||
|
l[i] = (2 * (pointsX[i+1] - pointsX[i-1])) - (h[i-1]) * u[i-1];
|
||||||
|
u[i] = (h[i]) / l[i];
|
||||||
|
a[i] = (3.0 / (h[i])) * (pointsY[i+1] - pointsY[i]) - (3.0 / (h[i-1])) * (pointsY[i] - pointsY[i-1]);
|
||||||
|
z[i] = (a[i] - (h[i-1]) * z[i-1]) / l[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
l[n] = 1.0;
|
||||||
|
z[n] = 0.0;
|
||||||
|
c[n] = 0.0;
|
||||||
|
|
||||||
|
for (int j = n - 1; j >= 0; j--)
|
||||||
|
{
|
||||||
|
c[j] = z[j] - u[j] * c[j+1];
|
||||||
|
b[j] = (pointsY[j+1] - pointsY[j]) / (h[j]) - ((h[j]) * (c[j+1] + 2.0 * c[j])) / 3.0;
|
||||||
|
d[j] = (c[j+1] - c[j]) / (3.0 * h[j]);
|
||||||
|
}
|
||||||
|
|
||||||
|
elements.reserve (n);
|
||||||
|
for (int i = 0; i < n; i++)
|
||||||
|
elements.emplace_back (pointsX[i], pointsY[i], b[i], c[i], d[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
double Spline::interpolate (double x) const noexcept
|
||||||
|
{
|
||||||
|
int n = static_cast<int>(elements.size());
|
||||||
|
if (n == 0)
|
||||||
|
return 0.0;
|
||||||
|
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < n; i++)
|
||||||
|
{
|
||||||
|
if (! (elements[i] < x))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (i != 0) i--;
|
||||||
|
|
||||||
|
return elements[i].eval (x);
|
||||||
|
}
|
||||||
46
src/external/spline/spline/spline.h
vendored
Normal file
46
src/external/spline/spline/spline.h
vendored
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
/*==============================================================================
|
||||||
|
|
||||||
|
Copyright 2018 by Roland Rabien, Devin Lane
|
||||||
|
For more information visit www.rabiensoftware.com
|
||||||
|
|
||||||
|
==============================================================================*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
/** Cubic spline interpolation is a simple way to obtain a smooth curve from a set of
|
||||||
|
discrete points. It has both C1 (first derivative) and C2 (second derivative) continuity,
|
||||||
|
enabling it to produce a continuous piecewise function given a set of data points.
|
||||||
|
|
||||||
|
Add points in increasing x order */
|
||||||
|
class Spline
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Spline (const double *pointsX, const double *pointsY, int numPoints);
|
||||||
|
|
||||||
|
double operator[] (double x) const noexcept { return interpolate(x); }
|
||||||
|
double interpolate (double x) const noexcept;
|
||||||
|
|
||||||
|
class Element
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Element (double x_ = 0) : x (x_) {}
|
||||||
|
|
||||||
|
Element (double x_, double a_, double b_, double c_, double d_)
|
||||||
|
: x (x_), a (a_), b (b_), c (c_), d (d_) {}
|
||||||
|
|
||||||
|
double eval (double xx) const
|
||||||
|
{
|
||||||
|
double xix (xx - x);
|
||||||
|
return a + b * xix + c * (xix * xix) + d * (xix * xix * xix);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator< (const Element& e) const { return x < e.x; }
|
||||||
|
bool operator< (const double xx) const { return x < xx; }
|
||||||
|
|
||||||
|
double x = 0, a = 0, b = 0, c = 0, d = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<Element> elements;
|
||||||
|
};
|
||||||
249
src/sfizz/Curve.cpp
Normal file
249
src/sfizz/Curve.cpp
Normal file
|
|
@ -0,0 +1,249 @@
|
||||||
|
// SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
|
||||||
|
// This code is part of the sfizz library and is licensed under a BSD 2-clause
|
||||||
|
// license. You should have receive a LICENSE.md file along with the code.
|
||||||
|
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
|
||||||
|
|
||||||
|
#include "Curve.h"
|
||||||
|
#include "Opcode.h"
|
||||||
|
#include "Debug.h"
|
||||||
|
#include <spline/spline.h>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
namespace sfz
|
||||||
|
{
|
||||||
|
|
||||||
|
static const Curve defaultCurve = Curve::buildBipolar(0.0, 1.0);
|
||||||
|
|
||||||
|
Curve Curve::buildCurveFromHeader(
|
||||||
|
absl::Span<const Opcode> members, Interpolator itp, bool limit)
|
||||||
|
{
|
||||||
|
Curve curve;
|
||||||
|
bool fillStatus[NumValues] = {};
|
||||||
|
const Range<float> fullRange { -HUGE_VALF, +HUGE_VALF };
|
||||||
|
|
||||||
|
auto setPoint = [&curve, &fillStatus](int i, float x) {
|
||||||
|
curve._points[i] = x;
|
||||||
|
fillStatus[i] = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// fill curve ends with default values (verified)
|
||||||
|
setPoint(0, 0.0);
|
||||||
|
setPoint(NumValues - 1, 1.0);
|
||||||
|
|
||||||
|
for (const Opcode& opc : members) {
|
||||||
|
unsigned index;
|
||||||
|
|
||||||
|
if (opc.lettersOnlyHash != hash("v"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto indexOpt = opc.backParameter();
|
||||||
|
if (!indexOpt || (index = *indexOpt) >= NumValues)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto valueOpt = readOpcode<float>(opc.value, fullRange);
|
||||||
|
if (!valueOpt)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
setPoint(index, *valueOpt);
|
||||||
|
}
|
||||||
|
|
||||||
|
curve.fill(itp, fillStatus);
|
||||||
|
|
||||||
|
if (limit) {
|
||||||
|
for (unsigned i = 0; i < NumValues; ++i)
|
||||||
|
curve._points[i] = clamp(curve._points[i], -1.0f, +1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
return curve;
|
||||||
|
}
|
||||||
|
|
||||||
|
Curve Curve::buildPredefinedCurve(int index)
|
||||||
|
{
|
||||||
|
Curve curve;
|
||||||
|
|
||||||
|
switch (index) {
|
||||||
|
default:
|
||||||
|
ASSERTFALSE;
|
||||||
|
case 0:
|
||||||
|
curve = buildBipolar(0, 1);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
curve = buildBipolar(-1, +1);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
curve = buildBipolar(1, 0);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
curve = buildBipolar(+1, -1);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
for (unsigned i = 0; i < NumValues; ++i) {
|
||||||
|
double x = i * (1.0 / (NumValues - 1));
|
||||||
|
curve._points[i] = x * x;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
for (unsigned i = 0; i < NumValues; ++i) {
|
||||||
|
double x = i * (1.0 / (NumValues - 1));
|
||||||
|
curve._points[i] = std::pow(x, 0.5);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
for (unsigned i = 0; i < NumValues; ++i) {
|
||||||
|
double x = i * (1.0 / (NumValues - 1));
|
||||||
|
curve._points[i] = std::pow(1.0 - x, 0.5);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return curve;
|
||||||
|
}
|
||||||
|
|
||||||
|
Curve Curve::buildBipolar(float v1, float v2)
|
||||||
|
{
|
||||||
|
Curve curve;
|
||||||
|
bool fillStatus[NumValues] = {};
|
||||||
|
|
||||||
|
curve._points[0] = v1;
|
||||||
|
curve._points[NumValues - 1] = v2;
|
||||||
|
|
||||||
|
fillStatus[0] = true;
|
||||||
|
fillStatus[NumValues - 1] = true;
|
||||||
|
|
||||||
|
curve.lerpFill(fillStatus);
|
||||||
|
return curve;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Curve& Curve::getDefault()
|
||||||
|
{
|
||||||
|
return defaultCurve;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Curve::fill(Interpolator itp, const bool fillStatus[NumValues])
|
||||||
|
{
|
||||||
|
switch (itp) {
|
||||||
|
default:
|
||||||
|
case Interpolator::Linear:
|
||||||
|
lerpFill(fillStatus);
|
||||||
|
break;
|
||||||
|
case Interpolator::Spline:
|
||||||
|
splineFill(fillStatus);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Curve::lerpFill(const bool fillStatus[NumValues])
|
||||||
|
{
|
||||||
|
for (int iCurr = 1; iCurr < NumValues - 1; ++iCurr) {
|
||||||
|
int iLeft, iRight;
|
||||||
|
iLeft = iCurr - 1;
|
||||||
|
for (iRight = iCurr + 1; iRight < 127 && !fillStatus[iRight]; ++iRight);
|
||||||
|
|
||||||
|
float mu = static_cast<float>(iCurr - iLeft) / (iRight - iLeft);
|
||||||
|
_points[iCurr] = _points[iLeft] + mu * (_points[iRight] - _points[iLeft]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Curve::splineFill(const bool fillStatus[NumValues])
|
||||||
|
{
|
||||||
|
std::array<double, NumValues> x;
|
||||||
|
std::array<double, NumValues> y;
|
||||||
|
unsigned count = 0;
|
||||||
|
|
||||||
|
for (unsigned i = 0; i < NumValues; ++i) {
|
||||||
|
if (fillStatus[i]) {
|
||||||
|
x[count] = i;
|
||||||
|
y[count] = _points[i];
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spline spline(x.data(), y.data(), count);
|
||||||
|
for (unsigned i = 0; i < NumValues; ++i) {
|
||||||
|
if (!fillStatus[i])
|
||||||
|
_points[i] = spline.interpolate(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
CurveSet CurveSet::createPredefined()
|
||||||
|
{
|
||||||
|
CurveSet cs;
|
||||||
|
cs._curves.reserve(16);
|
||||||
|
|
||||||
|
for (unsigned i = 0; i < Curve::NumPredefinedCurves; ++i) {
|
||||||
|
Curve curve = Curve::buildPredefinedCurve(i);
|
||||||
|
cs._curves.emplace_back(new Curve(curve));
|
||||||
|
}
|
||||||
|
|
||||||
|
return cs;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CurveSet::addCurve(const Curve& curve, int explicitIndex)
|
||||||
|
{
|
||||||
|
std::unique_ptr<Curve>* slot;
|
||||||
|
|
||||||
|
if (explicitIndex == -1) {
|
||||||
|
if (_useExplicitIndexing)
|
||||||
|
return; // reject implicit indices if any were explicit before
|
||||||
|
_curves.emplace_back();
|
||||||
|
slot = &_curves.back();
|
||||||
|
} else {
|
||||||
|
size_t index = static_cast<unsigned>(explicitIndex);
|
||||||
|
if (index >= _curves.size())
|
||||||
|
_curves.resize(index + 1);
|
||||||
|
_useExplicitIndexing = true;
|
||||||
|
slot = &_curves[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
slot->reset(new Curve(curve));
|
||||||
|
}
|
||||||
|
|
||||||
|
void CurveSet::addCurveFromHeader(absl::Span<const Opcode> members)
|
||||||
|
{
|
||||||
|
auto findOpcode = [members](uint64_t name_hash) -> const Opcode* {
|
||||||
|
const Opcode* opc = nullptr;
|
||||||
|
for (size_t i = members.size(); !opc && i-- > 0;) {
|
||||||
|
if (members[i].lettersOnlyHash == name_hash)
|
||||||
|
opc = &members[i];
|
||||||
|
}
|
||||||
|
return opc;
|
||||||
|
};
|
||||||
|
|
||||||
|
int curveIndex = -1;
|
||||||
|
Curve::Interpolator itp = Curve::Interpolator::Linear;
|
||||||
|
|
||||||
|
if (const Opcode* opc = findOpcode(hash("curve_index"))) {
|
||||||
|
if (auto opt = readOpcode<int>(opc->value, {0, 255}))
|
||||||
|
curveIndex = *opt;
|
||||||
|
else
|
||||||
|
DBG("Invalid value for curve index: " << opc.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0 // potential sfizz extension
|
||||||
|
if (const Opcode* opc = findOpcode(hash("sfizz:curve_interpolator"))) {
|
||||||
|
if (opc->value == "spline")
|
||||||
|
itp = Curve::Interpolator::Spline;
|
||||||
|
else if (opc->value != "linear")
|
||||||
|
DBG("Invalid value for curve interpolator: " << opc.value);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
addCurve(Curve::buildCurveFromHeader(members, itp), curveIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Curve& CurveSet::getCurve(unsigned index) const
|
||||||
|
{
|
||||||
|
const Curve* curve = nullptr;
|
||||||
|
|
||||||
|
if (index < _curves.size())
|
||||||
|
curve = _curves[index].get();
|
||||||
|
|
||||||
|
if (!curve)
|
||||||
|
curve = &Curve::getDefault();
|
||||||
|
|
||||||
|
return *curve;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sfz
|
||||||
125
src/sfizz/Curve.h
Normal file
125
src/sfizz/Curve.h
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
// SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
|
||||||
|
// This code is part of the sfizz library and is licensed under a BSD 2-clause
|
||||||
|
// license. You should have receive a LICENSE.md file along with the code.
|
||||||
|
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "absl/types/span.h"
|
||||||
|
#include <array>
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace sfz
|
||||||
|
{
|
||||||
|
struct Opcode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A value-mapping controller curve, built-in or user-defined.
|
||||||
|
*/
|
||||||
|
class Curve {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Compute the curve for integral x in domain [0:127]
|
||||||
|
*/
|
||||||
|
float evalCC7(int value7) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Compute the curve for real x in domain [0:127]
|
||||||
|
*/
|
||||||
|
float evalCC7(float value7) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Compute the curve for real x in domain [0:1]
|
||||||
|
*/
|
||||||
|
float evalNormalized(float value) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Kind of curve interpolator
|
||||||
|
*/
|
||||||
|
enum class Interpolator {
|
||||||
|
Linear,
|
||||||
|
Spline,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Build a curve based on contents of a <curve> header
|
||||||
|
*
|
||||||
|
* @param members contents of the <curve> header
|
||||||
|
* @param itp kind of interpolator to fill between values
|
||||||
|
* @param limit whether to force values in domain [-1:+1]
|
||||||
|
*/
|
||||||
|
static Curve buildCurveFromHeader(
|
||||||
|
absl::Span<const Opcode> members,
|
||||||
|
Interpolator itp = Interpolator::Linear, bool limit = false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Number of predefined curves
|
||||||
|
*/
|
||||||
|
enum { NumPredefinedCurves = 7 };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Build a predefined curve
|
||||||
|
*/
|
||||||
|
static Curve buildPredefinedCurve(int index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Build a linear curve from v1 to v2
|
||||||
|
*/
|
||||||
|
static Curve buildBipolar(float v1, float v2);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get a linear curve from 0 to 1
|
||||||
|
*/
|
||||||
|
static const Curve& getDefault();
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum { NumValues = 128 };
|
||||||
|
|
||||||
|
private:
|
||||||
|
void fill(Interpolator itp, const bool fillStatus[NumValues]);
|
||||||
|
void lerpFill(const bool fillStatus[NumValues]);
|
||||||
|
void splineFill(const bool fillStatus[NumValues]);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::array<float, NumValues> _points { };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A collection of curves organized by index.
|
||||||
|
*/
|
||||||
|
class CurveSet {
|
||||||
|
public:
|
||||||
|
/*
|
||||||
|
* @brief Create a curve set initialized with the default curves.
|
||||||
|
*/
|
||||||
|
static CurveSet createPredefined();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @brief Add a curve.
|
||||||
|
*
|
||||||
|
* @param curve a curve to add
|
||||||
|
* @param explicitIndex if not -1, the explicit index of the curve
|
||||||
|
*/
|
||||||
|
void addCurve(const Curve& curve, int explicitIndex = -1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Add a curve based on contents of a <curve> header
|
||||||
|
*
|
||||||
|
* @param members contents of the <curve> header
|
||||||
|
*/
|
||||||
|
void addCurveFromHeader(absl::Span<const Opcode> members);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get a curve given its index
|
||||||
|
*/
|
||||||
|
const Curve& getCurve(unsigned index) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<std::unique_ptr<Curve>> _curves;
|
||||||
|
bool _useExplicitIndexing = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sfz
|
||||||
|
|
||||||
|
#include "Curve.hpp"
|
||||||
33
src/sfizz/Curve.hpp
Normal file
33
src/sfizz/Curve.hpp
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
// SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
|
||||||
|
// This code is part of the sfizz library and is licensed under a BSD 2-clause
|
||||||
|
// license. You should have receive a LICENSE.md file along with the code.
|
||||||
|
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "Curve.h"
|
||||||
|
#include "MathHelpers.h"
|
||||||
|
|
||||||
|
namespace sfz
|
||||||
|
{
|
||||||
|
|
||||||
|
inline float Curve::evalCC7(int value7) const
|
||||||
|
{
|
||||||
|
return _points[clamp(value7, 0, 127)];
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float Curve::evalCC7(float value7) const
|
||||||
|
{
|
||||||
|
value7 = clamp(value7, 0.0f, 127.0f);
|
||||||
|
int i1 = static_cast<int>(value7);
|
||||||
|
int i2 = std::min(127, i1 + 1);
|
||||||
|
float mu = value7 - i1;
|
||||||
|
return _points[i1] + mu * (_points[i2] - _points[i1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float Curve::evalNormalized(float value) const
|
||||||
|
{
|
||||||
|
return evalCC7(127 * value);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sfz
|
||||||
|
|
@ -56,5 +56,7 @@ target_link_libraries(eq_apply PRIVATE sfizz::sfizz)
|
||||||
add_executable(filter_apply Filter.cpp)
|
add_executable(filter_apply Filter.cpp)
|
||||||
target_link_libraries(filter_apply PRIVATE sfizz::sfizz)
|
target_link_libraries(filter_apply PRIVATE sfizz::sfizz)
|
||||||
|
|
||||||
|
add_executable(sfizz_plot_curve PlotCurve.cpp)
|
||||||
|
target_link_libraries(sfizz_plot_curve PRIVATE sfizz::sfizz)
|
||||||
|
|
||||||
file(COPY "." DESTINATION ${CMAKE_BINARY_DIR}/tests)
|
file(COPY "." DESTINATION ${CMAKE_BINARY_DIR}/tests)
|
||||||
|
|
|
||||||
94
tests/PlotCurve.cpp
Normal file
94
tests/PlotCurve.cpp
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
// SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
|
||||||
|
// This code is part of the sfizz library and is licensed under a BSD 2-clause
|
||||||
|
// license. You should have receive a LICENSE.md file along with the code.
|
||||||
|
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
|
||||||
|
|
||||||
|
/**
|
||||||
|
This program generates the data file of a LFO output recorded for a fixed
|
||||||
|
duration. The file contains columns for each LFO in the SFZ region.
|
||||||
|
The columns are: Time, Lfo1, ... LfoN
|
||||||
|
One can use Gnuplot to display this data.
|
||||||
|
Example:
|
||||||
|
sfizz_plot_lfo file.sfz > lfo.dat
|
||||||
|
gnuplot
|
||||||
|
plot "lfo.dat" using 1:2 with lines
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "sfizz/Curve.h"
|
||||||
|
#include "sfizz/Parser.h"
|
||||||
|
#include "absl/strings/numbers.h"
|
||||||
|
#include "absl/types/span.h"
|
||||||
|
#include <iostream>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Print usage information
|
||||||
|
*/
|
||||||
|
static void usage()
|
||||||
|
{
|
||||||
|
std::cerr << "Usage: sfizz_plot_curve <index> [file.sfz]"
|
||||||
|
<< "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Parser which extracts the configuration of curves
|
||||||
|
*/
|
||||||
|
class CurveParser : public sfz::Parser {
|
||||||
|
public:
|
||||||
|
explicit CurveParser(sfz::CurveSet& curveSet)
|
||||||
|
: curveSet(curveSet)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void callback(absl::string_view header, const std::vector<sfz::Opcode>& members) override
|
||||||
|
{
|
||||||
|
if (header == "effect")
|
||||||
|
curveSet.addCurveFromHeader(members);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
sfz::CurveSet& curveSet;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Program which loads LFO configuration and generates plot data for the given duration.
|
||||||
|
*/
|
||||||
|
int main(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
if (argc < 2 || argc > 3)
|
||||||
|
return usage(), 1;
|
||||||
|
|
||||||
|
int curveIndex = -1;
|
||||||
|
if (!absl::SimpleAtoi(argv[1], &curveIndex))
|
||||||
|
return usage(), 1;
|
||||||
|
|
||||||
|
std::string filePath;
|
||||||
|
if (argc > 2)
|
||||||
|
filePath = argv[2];
|
||||||
|
|
||||||
|
///
|
||||||
|
sfz::CurveSet curveSet = sfz::CurveSet::createPredefined();
|
||||||
|
|
||||||
|
if (!filePath.empty()) {
|
||||||
|
CurveParser parser(curveSet);
|
||||||
|
if (!parser.loadSfzFile(filePath)) {
|
||||||
|
std::cerr << "Cannot load SFZ: " << filePath << "\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
const sfz::Curve& curve = curveSet.getCurve(curveIndex);
|
||||||
|
|
||||||
|
constexpr unsigned numSamples = 1024; // number of points to evaluate
|
||||||
|
|
||||||
|
for (unsigned i = 0; i < numSamples; ++i) {
|
||||||
|
float x = i * (1.0f / (numSamples - 1));
|
||||||
|
std::cout << x << ' ' << curve.evalNormalized(x) << '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue