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()
|
||||
|
||||
|
||||
# If we build with Clang use libc++
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND NOT ANDROID)
|
||||
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")
|
||||
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)
|
||||
if (UNIX AND NOT APPLE)
|
||||
check_library_exists(atomic __atomic_load "" LIBATOMIC_FOUND)
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ set (SFIZZ_SOURCES
|
|||
sfizz/FloatEnvelopes.cpp
|
||||
sfizz/Logger.cpp
|
||||
sfizz/SfzFilter.cpp
|
||||
sfizz/Curve.cpp
|
||||
sfizz/Effects.cpp
|
||||
sfizz/effects/Nothing.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 external)
|
||||
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")
|
||||
if (WIN32)
|
||||
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_include_directories (sfizz_shared PRIVATE .)
|
||||
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)
|
||||
target_compile_definitions (sfizz_shared PRIVATE _USE_MATH_DEFINES)
|
||||
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)
|
||||
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)
|
||||
|
|
|
|||
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