commit
5317d2785d
16 changed files with 937 additions and 31 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)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
clang-tidy \
|
||||
src/sfizz/ADSREnvelope.cpp \
|
||||
src/sfizz/Curve.cpp \
|
||||
src/sfizz/Effects.cpp \
|
||||
src/sfizz/EQPool.cpp \
|
||||
src/sfizz/EventEnvelopes.cpp \
|
||||
|
|
@ -28,5 +29,5 @@ clang-tidy \
|
|||
vst/SfizzVstEditor.cpp \
|
||||
vst/SfizzVstState.cpp \
|
||||
-- -Iexternal/abseil-cpp -Isrc/external -Isrc/external/pugixml/src \
|
||||
-Isrc/sfizz -Isrc \
|
||||
-Isrc/sfizz -Isrc -Isrc/external/spline \
|
||||
-Ivst -Ivst/external/VST_SDK/VST3_SDK -Ivst/external/VST_SDK/VST3_SDK/vstgui4 -DNDEBUG
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -85,7 +85,7 @@ public:
|
|||
*/
|
||||
iterator channelWriter(size_t channelIndex)
|
||||
{
|
||||
ASSERT(channelIndex < numChannels)
|
||||
ASSERT(channelIndex < numChannels);
|
||||
if (channelIndex < numChannels)
|
||||
return buffers[channelIndex]->data();
|
||||
|
||||
|
|
@ -100,7 +100,7 @@ public:
|
|||
*/
|
||||
iterator channelWriterEnd(size_t channelIndex)
|
||||
{
|
||||
ASSERT(channelIndex < numChannels)
|
||||
ASSERT(channelIndex < numChannels);
|
||||
if (channelIndex < numChannels)
|
||||
return buffers[channelIndex]->end();
|
||||
|
||||
|
|
@ -115,7 +115,7 @@ public:
|
|||
*/
|
||||
const_iterator channelReader(size_t channelIndex) const
|
||||
{
|
||||
ASSERT(channelIndex < numChannels)
|
||||
ASSERT(channelIndex < numChannels);
|
||||
if (channelIndex < numChannels)
|
||||
return buffers[channelIndex]->data();
|
||||
|
||||
|
|
@ -130,7 +130,7 @@ public:
|
|||
*/
|
||||
const_iterator channelReaderEnd(size_t channelIndex) const
|
||||
{
|
||||
ASSERT(channelIndex < numChannels)
|
||||
ASSERT(channelIndex < numChannels);
|
||||
if (channelIndex < numChannels)
|
||||
return buffers[channelIndex]->end();
|
||||
|
||||
|
|
@ -145,7 +145,7 @@ public:
|
|||
*/
|
||||
absl::Span<value_type> getSpan(size_t channelIndex) const
|
||||
{
|
||||
ASSERT(channelIndex < numChannels)
|
||||
ASSERT(channelIndex < numChannels);
|
||||
if (channelIndex < numChannels)
|
||||
return { buffers[channelIndex]->data(), buffers[channelIndex]->size() };
|
||||
|
||||
|
|
|
|||
257
src/sfizz/Curve.cpp
Normal file
257
src/sfizz/Curve.cpp
Normal file
|
|
@ -0,0 +1,257 @@
|
|||
// 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 "SIMDHelpers.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) {
|
||||
if (opc.lettersOnlyHash != hash("v&"))
|
||||
continue;
|
||||
|
||||
unsigned index = opc.parameters.back();
|
||||
if (index >= NumValues)
|
||||
continue;
|
||||
|
||||
auto valueOpt = readOpcode<float>(opc.value, fullRange);
|
||||
if (!valueOpt)
|
||||
continue;
|
||||
|
||||
setPoint(static_cast<int>(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;
|
||||
// fallthrough
|
||||
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])
|
||||
{
|
||||
int left { 0 };
|
||||
int right { 1 };
|
||||
auto pointSpan = absl::MakeSpan(_points);
|
||||
|
||||
while (right < NumValues) {
|
||||
for (; right < NumValues && !fillStatus[right]; ++right);
|
||||
const auto length = right - left;
|
||||
if (length > 1) {
|
||||
const float mu = (_points[right] - _points[left]) / length;
|
||||
linearRamp<float>(pointSpan.subspan(left + 1, length - 1), _points[left], mu);
|
||||
}
|
||||
left = right++;
|
||||
}
|
||||
}
|
||||
|
||||
void Curve::splineFill(const bool fillStatus[NumValues])
|
||||
{
|
||||
std::array<double, NumValues> x;
|
||||
std::array<double, NumValues> y;
|
||||
int count = 0;
|
||||
|
||||
for (unsigned i = 0; i < NumValues; ++i) {
|
||||
if (fillStatus[i]) {
|
||||
x[count] = i;
|
||||
y[count] = _points[i];
|
||||
++count;
|
||||
}
|
||||
}
|
||||
|
||||
if (count < 3)
|
||||
return lerpFill(fillStatus);
|
||||
|
||||
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 (int 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
|
||||
145
src/sfizz/Curve.h
Normal file
145
src/sfizz/Curve.h
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
// 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 "MathHelpers.h"
|
||||
#include "SfzHelpers.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
|
||||
{
|
||||
return _points[clamp(value7, 0, 127)];
|
||||
}
|
||||
/**
|
||||
* @brief Compute the curve for real x in domain [0:127]
|
||||
*/
|
||||
float 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]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Compute the curve for real x in domain [0:1]
|
||||
*/
|
||||
float evalNormalized(float value) const
|
||||
{
|
||||
return evalCC7(denormalize7Bits<int>(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
|
||||
/**
|
||||
* @brief Get the number of slots.
|
||||
*/
|
||||
unsigned getNumCurves() const
|
||||
{
|
||||
return static_cast<unsigned>(_curves.size());
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<Curve>> _curves;
|
||||
bool _useExplicitIndexing = false;
|
||||
};
|
||||
|
||||
} // namespace sfz
|
||||
|
|
@ -14,16 +14,16 @@
|
|||
// Break in source code
|
||||
#if (__x86_64__ || __i386__)
|
||||
#define ASSERTFALSE \
|
||||
{ \
|
||||
do { \
|
||||
std::cerr << "Assert failed at " << __FILE__ << ":" << __LINE__ << '\n'; \
|
||||
__asm__("int3"); \
|
||||
}
|
||||
} while (0)
|
||||
#elif (__arm__ || __aarch64__)
|
||||
#define ASSERTFALSE \
|
||||
{ \
|
||||
do { \
|
||||
std::cerr << "Assert failed at " << __FILE__ << ":" << __LINE__ << '\n'; \
|
||||
__builtin_trap(); \
|
||||
}
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
#elif (_WIN32 || _WIN64)
|
||||
|
|
@ -31,26 +31,28 @@
|
|||
#pragma intrinsic(__debugbreak)
|
||||
#endif
|
||||
#define ASSERTFALSE \
|
||||
{ \
|
||||
do { \
|
||||
std::cerr << "Assert failed at " << __FILE__ << ":" << __LINE__ << '\n'; \
|
||||
__debugbreak(); \
|
||||
}
|
||||
} while (0)
|
||||
#else
|
||||
#define ASSERTFALSE
|
||||
#define ASSERTFALSE do {} while (0)
|
||||
#endif
|
||||
|
||||
// Assert stuff
|
||||
#define ASSERT(expression) \
|
||||
if (!(expression)) \
|
||||
ASSERTFALSE
|
||||
do { \
|
||||
if (!(expression)) \
|
||||
ASSERTFALSE \
|
||||
while (0)
|
||||
|
||||
// Debug message
|
||||
#define DBG(ostream) std::cerr << ostream << '\n'
|
||||
#define DBG(ostream) do { std::cerr << ostream << '\n'; } while (0)
|
||||
|
||||
#else // NDEBUG
|
||||
|
||||
#define ASSERTFALSE
|
||||
#define ASSERT(expression)
|
||||
#define DBG(ostream)
|
||||
#define ASSERTFALSE do {} while (0)
|
||||
#define ASSERT(expression) do {} while (0)
|
||||
#define DBG(ostream) do {} while (0)
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
#include "Macros.h"
|
||||
#include "Config.h"
|
||||
#include "MathHelpers.h"
|
||||
#include "absl/meta/type_traits.h"
|
||||
|
||||
namespace sfz
|
||||
{
|
||||
|
|
@ -76,8 +77,20 @@ constexpr float centsFactor(T cents, T centsPerOctave = 1200)
|
|||
return std::pow(2.0f, static_cast<float>(cents) / centsPerOctave);
|
||||
}
|
||||
|
||||
template<class T, absl::enable_if_t<std::is_integral<T>::value, int> = 0>
|
||||
constexpr T denormalize7Bits(float value)
|
||||
{
|
||||
return static_cast<T>(value * 127.0f);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
constexpr float normalize7Bits(T value)
|
||||
{
|
||||
return static_cast<float>(min(max(value, T{ 0 }), T{ 127 })) / 127.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Normalize a CC value between (T)0.0 and (T)1.0
|
||||
* @brief Normalize a CC value between 0.0 and 1.0
|
||||
*
|
||||
* @tparam T
|
||||
* @param ccValue
|
||||
|
|
@ -86,12 +99,11 @@ constexpr float centsFactor(T cents, T centsPerOctave = 1200)
|
|||
template<class T>
|
||||
constexpr float normalizeCC(T ccValue)
|
||||
{
|
||||
static_assert(std::is_integral<T>::value, "Requires an integral T");
|
||||
return static_cast<float>(min(max(ccValue, static_cast<T>(0)), static_cast<T>(127))) / 127.0f;
|
||||
return normalize7Bits(ccValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Normalize a velocity between (T)0.0 and (T)1.0
|
||||
* @brief Normalize a velocity between 0.0 and 1.0
|
||||
*
|
||||
* @tparam T
|
||||
* @param ccValue
|
||||
|
|
@ -100,7 +112,7 @@ constexpr float normalizeCC(T ccValue)
|
|||
template<class T>
|
||||
constexpr float normalizeVelocity(T velocity)
|
||||
{
|
||||
return normalizeCC(velocity);
|
||||
return normalize7Bits(velocity);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -71,8 +71,7 @@ void sfz::Synth::callback(absl::string_view header, const std::vector<Opcode>& m
|
|||
buildRegion(members);
|
||||
break;
|
||||
case hash("curve"):
|
||||
// TODO: implement curves
|
||||
numCurves++;
|
||||
curves.addCurveFromHeader(members);
|
||||
break;
|
||||
case hash("effect"):
|
||||
handleEffectOpcodes(members);
|
||||
|
|
@ -128,11 +127,11 @@ void sfz::Synth::clear()
|
|||
effectBuses[0]->setGainToMain(1.0);
|
||||
effectBuses[0]->setSamplesPerBlock(samplesPerBlock);
|
||||
effectBuses[0]->setSampleRate(sampleRate);
|
||||
curves = CurveSet::createPredefined();
|
||||
resources.filePool.clear();
|
||||
resources.logger.clear();
|
||||
numGroups = 0;
|
||||
numMasters = 0;
|
||||
numCurves = 0;
|
||||
fileTicket = -1;
|
||||
defaultSwitch = absl::nullopt;
|
||||
defaultPath = "";
|
||||
|
|
@ -717,7 +716,7 @@ int sfz::Synth::getNumMasters() const noexcept
|
|||
}
|
||||
int sfz::Synth::getNumCurves() const noexcept
|
||||
{
|
||||
return numCurves;
|
||||
return static_cast<int>(curves.getNumCurves());
|
||||
}
|
||||
|
||||
std::string sfz::Synth::exportMidnam(absl::string_view model) const
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
#include "Voice.h"
|
||||
#include "Region.h"
|
||||
#include "Effects.h"
|
||||
#include "Curve.h"
|
||||
#include "LeakDetector.h"
|
||||
#include "MidiState.h"
|
||||
#include "AudioSpan.h"
|
||||
|
|
@ -382,7 +383,6 @@ private:
|
|||
|
||||
int numGroups { 0 };
|
||||
int numMasters { 0 };
|
||||
int numCurves { 0 };
|
||||
|
||||
/**
|
||||
* @brief Remove all regions, resets all voices and clears everything
|
||||
|
|
@ -461,6 +461,9 @@ private:
|
|||
typedef std::unique_ptr<EffectBus> EffectBusPtr;
|
||||
std::vector<EffectBusPtr> effectBuses; // 0 is "main", 1-N are "fx1"-"fxN"
|
||||
|
||||
// Curves
|
||||
CurveSet curves;
|
||||
|
||||
// Intermediate buffers
|
||||
AudioBuffer<float> tempBuffer { 2, config::defaultSamplesPerBlock };
|
||||
AudioBuffer<float> tempMixNodeBuffer { 2, config::defaultSamplesPerBlock };
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ set(SFIZZ_TEST_SOURCES
|
|||
EventEnvelopesT.cpp
|
||||
MainT.cpp
|
||||
SynthT.cpp
|
||||
CurveT.cpp
|
||||
RegionTriggersT.cpp
|
||||
FloatHelpersT.cpp
|
||||
)
|
||||
|
|
@ -56,5 +57,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)
|
||||
|
|
|
|||
233
tests/CurveT.cpp
Normal file
233
tests/CurveT.cpp
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
// 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 "sfizz/Curve.h"
|
||||
#include "sfizz/Opcode.h"
|
||||
#include "catch2/catch.hpp"
|
||||
#include <algorithm>
|
||||
using namespace Catch::literals;
|
||||
|
||||
TEST_CASE("[Curve] Bipolar 0 to 1")
|
||||
{
|
||||
auto curve = sfz::Curve::buildPredefinedCurve(0);
|
||||
REQUIRE( curve.evalCC7(0) == 0.0f );
|
||||
REQUIRE( curve.evalCC7(127) == 1.0f );
|
||||
REQUIRE( curve.evalCC7(2) == Approx(0.0157).margin(1e-3) );
|
||||
REQUIRE( curve.evalCC7(63) == Approx(0.496).margin(1e-3) );
|
||||
REQUIRE( curve.evalCC7(85) == Approx(0.669).margin(1e-3) );
|
||||
REQUIRE( curve.evalNormalized(0.0f) == 0.0f );
|
||||
REQUIRE( curve.evalNormalized(1.0f) == 1.0f );
|
||||
REQUIRE( curve.evalNormalized(0.3f) == Approx(0.299).margin(1e-3) );
|
||||
}
|
||||
|
||||
TEST_CASE("[Curve] Bipolar -1 to 1")
|
||||
{
|
||||
auto curve = sfz::Curve::buildPredefinedCurve(1);
|
||||
REQUIRE( curve.evalCC7(0) == -1.0f );
|
||||
REQUIRE( curve.evalCC7(127) == 1.0f );
|
||||
REQUIRE( curve.evalCC7(2) == Approx(-0.9685).margin(1e-3) );
|
||||
REQUIRE( curve.evalCC7(63) == Approx(-0.00787).margin(1e-5) );
|
||||
REQUIRE( curve.evalCC7(85) == Approx(0.3386).margin(1e-3) );
|
||||
REQUIRE( curve.evalNormalized(0.0f) == -1.0f );
|
||||
REQUIRE( curve.evalNormalized(1.0f) == 1.0f );
|
||||
REQUIRE( curve.evalNormalized(0.3f) == Approx(-0.402).margin(1e-3) );
|
||||
}
|
||||
|
||||
TEST_CASE("[Curve] Bipolar 1 to 0")
|
||||
{
|
||||
auto curve = sfz::Curve::buildPredefinedCurve(2);
|
||||
REQUIRE( curve.evalCC7(0) == 1.0f );
|
||||
REQUIRE( curve.evalCC7(127) == 0.0f );
|
||||
REQUIRE( curve.evalCC7(2) == Approx(0.984).margin(1e-3) );
|
||||
REQUIRE( curve.evalCC7(63) == Approx(0.504).margin(1e-3) );
|
||||
REQUIRE( curve.evalCC7(85) == Approx(0.331).margin(1e-3) );
|
||||
REQUIRE( curve.evalNormalized(0.0f) == 1.0f );
|
||||
REQUIRE( curve.evalNormalized(1.0f) == 0.0f );
|
||||
REQUIRE( curve.evalNormalized(0.3f) == Approx(0.701).margin(1e-3) );
|
||||
}
|
||||
|
||||
TEST_CASE("[Curve] Bipolar 1 to -1")
|
||||
{
|
||||
auto curve = sfz::Curve::buildPredefinedCurve(3);
|
||||
REQUIRE( curve.evalCC7(0) == 1.0f );
|
||||
REQUIRE( curve.evalCC7(127) == -1.0f );
|
||||
REQUIRE( curve.evalCC7(2) == Approx(0.9685).margin(1e-3) );
|
||||
REQUIRE( curve.evalCC7(63) == Approx(0.00787).margin(1e-5) );
|
||||
REQUIRE( curve.evalCC7(85) == Approx(-0.3386).margin(1e-3) );
|
||||
REQUIRE( curve.evalNormalized(0.0f) == 1.0f );
|
||||
REQUIRE( curve.evalNormalized(1.0f) == -1.0f );
|
||||
REQUIRE( curve.evalNormalized(0.3f) == Approx(0.402).margin(1e-3) );
|
||||
}
|
||||
|
||||
TEST_CASE("[Curve] x**2")
|
||||
{
|
||||
auto curve = sfz::Curve::buildPredefinedCurve(4);
|
||||
REQUIRE( curve.evalCC7(0) == 0.0f );
|
||||
REQUIRE( curve.evalCC7(127) == 1.0f );
|
||||
REQUIRE( curve.evalCC7(2) == Approx(0.000248).margin(1e-5) );
|
||||
REQUIRE( curve.evalCC7(63) == Approx(0.246).margin(1e-3) );
|
||||
REQUIRE( curve.evalCC7(85) == Approx(0.448).margin(1e-3) );
|
||||
REQUIRE( curve.evalNormalized(0.0f) == 0.0f );
|
||||
REQUIRE( curve.evalNormalized(1.0f) == 1.0f );
|
||||
REQUIRE( curve.evalNormalized(0.3f) == Approx(0.09).margin(1e-3) );
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("[Curve] sqrt(x)")
|
||||
{
|
||||
auto curve = sfz::Curve::buildPredefinedCurve(5);
|
||||
REQUIRE( curve.evalCC7(0) == 0.0f );
|
||||
REQUIRE( curve.evalCC7(127) == 1.0f );
|
||||
REQUIRE( curve.evalCC7(2) == Approx(0.125).margin(1e-3) );
|
||||
REQUIRE( curve.evalCC7(63) == Approx(0.704).margin(1e-3) );
|
||||
REQUIRE( curve.evalCC7(85) == Approx(0.818).margin(1e-3) );
|
||||
REQUIRE( curve.evalNormalized(0.0f) == 0.0f );
|
||||
REQUIRE( curve.evalNormalized(1.0f) == 1.0f );
|
||||
REQUIRE( curve.evalNormalized(0.3f) == Approx(0.5477).margin(1e-3) );
|
||||
}
|
||||
|
||||
TEST_CASE("[Curve] sqrt(1-x)")
|
||||
{
|
||||
auto curve = sfz::Curve::buildPredefinedCurve(6);
|
||||
REQUIRE( curve.evalCC7(0) == 1.0f );
|
||||
REQUIRE( curve.evalCC7(127) == 0.0f );
|
||||
REQUIRE( curve.evalCC7(2) == Approx(0.992).margin(1e-3) );
|
||||
REQUIRE( curve.evalCC7(63) == Approx(0.710).margin(1e-3) );
|
||||
REQUIRE( curve.evalCC7(85) == Approx(0.575).margin(1e-3) );
|
||||
REQUIRE( curve.evalNormalized(0.0f) == 1.0f );
|
||||
REQUIRE( curve.evalNormalized(1.0f) == 0.0f );
|
||||
REQUIRE( curve.evalNormalized(0.3f) == Approx(0.837).margin(1e-3) );
|
||||
}
|
||||
|
||||
TEST_CASE("[Curve] Custom")
|
||||
{
|
||||
auto curve = sfz::Curve::buildCurveFromHeader({
|
||||
{ "v000", "0" },
|
||||
{ "v063", "1" },
|
||||
{ "v127", "0" }
|
||||
}, sfz::Curve::Interpolator::Linear);
|
||||
REQUIRE( curve.evalCC7(0) == 0.0f );
|
||||
REQUIRE( curve.evalCC7(127) == 0.0f );
|
||||
REQUIRE( curve.evalCC7(63) == 1.0f );
|
||||
REQUIRE( curve.evalCC7(2) == Approx(0.032).margin(1e-3) );
|
||||
REQUIRE( curve.evalCC7(70) == Approx(0.891).margin(1e-3) );
|
||||
}
|
||||
|
||||
TEST_CASE("[Curve] Custom 2")
|
||||
{
|
||||
auto curve = sfz::Curve::buildCurveFromHeader({
|
||||
{ "v063", "1" }
|
||||
}, sfz::Curve::Interpolator::Linear);
|
||||
REQUIRE( curve.evalCC7(0) == 0.0f );
|
||||
REQUIRE( curve.evalCC7(127) == 1.0f );
|
||||
REQUIRE( curve.evalCC7(2) == Approx(0.032).margin(1e-3) );
|
||||
REQUIRE( curve.evalCC7(63) == 1.0f );
|
||||
REQUIRE( curve.evalCC7(70) == 1.0f );
|
||||
}
|
||||
|
||||
TEST_CASE("[Curve] Custom 3")
|
||||
{
|
||||
auto curve = sfz::Curve::buildCurveFromHeader({
|
||||
{ "v063", "1" },
|
||||
{ "v064", "0.5" }
|
||||
}, sfz::Curve::Interpolator::Linear);
|
||||
REQUIRE( curve.evalCC7(0) == 0.0f );
|
||||
REQUIRE( curve.evalCC7(127) == 1.0f );
|
||||
REQUIRE( curve.evalCC7(2) == Approx(0.032).margin(1e-3) );
|
||||
REQUIRE( curve.evalCC7(63) == 1.0f );
|
||||
REQUIRE( curve.evalCC7(64) == 0.5f );
|
||||
REQUIRE( curve.evalCC7(70) == Approx(0.548).margin(1e-3) );
|
||||
}
|
||||
|
||||
TEST_CASE("[Curve] Custom 4")
|
||||
{
|
||||
auto curve = sfz::Curve::buildCurveFromHeader({
|
||||
{ "v063", "1" },
|
||||
{ "v065", "0.5" }
|
||||
}, sfz::Curve::Interpolator::Linear);
|
||||
REQUIRE( curve.evalCC7(0) == 0.0f );
|
||||
REQUIRE( curve.evalCC7(127) == 1.0f );
|
||||
REQUIRE( curve.evalCC7(2) == Approx(0.032).margin(1e-3) );
|
||||
REQUIRE( curve.evalCC7(63) == 1.0f );
|
||||
REQUIRE( curve.evalCC7(64) == Approx(0.75).margin(1e-3) );
|
||||
REQUIRE( curve.evalCC7(65) == 0.5f );
|
||||
REQUIRE( curve.evalCC7(70) == Approx(0.54).margin(1e-3) );
|
||||
}
|
||||
|
||||
TEST_CASE("[Curve] Custom 5")
|
||||
{
|
||||
auto curve = sfz::Curve::buildCurveFromHeader({
|
||||
{ "v000", "1" },
|
||||
{ "v064", "0.9" },
|
||||
{ "v100", "0.9" },
|
||||
{ "v127", "0" }
|
||||
}, sfz::Curve::Interpolator::Linear);
|
||||
REQUIRE( curve.evalCC7(0) == 1.0f );
|
||||
REQUIRE( curve.evalCC7(15) == Approx(0.977).margin(1e-3) );
|
||||
REQUIRE( curve.evalCC7(64) == 0.9f );
|
||||
REQUIRE( curve.evalCC7(90) == 0.9f );
|
||||
REQUIRE( curve.evalCC7(100) == 0.9f );
|
||||
REQUIRE( curve.evalCC7(110) == Approx(0.567).margin(1e-3) );
|
||||
REQUIRE( curve.evalCC7(127) == 0.0f );
|
||||
}
|
||||
|
||||
TEST_CASE("[Curve] Add curves to CurveSet")
|
||||
{
|
||||
sfz::CurveSet curveSet;
|
||||
curveSet.addCurve(sfz::Curve::buildPredefinedCurve(0));
|
||||
curveSet.addCurve(sfz::Curve::buildPredefinedCurve(2));
|
||||
REQUIRE( curveSet.getNumCurves() == 2 );
|
||||
REQUIRE( curveSet.getCurve(0).evalCC7(0) == 0.0f );
|
||||
REQUIRE( curveSet.getCurve(1).evalCC7(0) == 1.0f );
|
||||
// Out of bound curve defaults to linear
|
||||
REQUIRE( curveSet.getCurve(2).evalCC7(0) == 0.0f );
|
||||
REQUIRE( curveSet.getCurve(2).evalCC7(127) == 1.0f );
|
||||
// Change a curve in a position
|
||||
curveSet.addCurve(sfz::Curve::buildPredefinedCurve(0), 1);
|
||||
REQUIRE(curveSet.getNumCurves() == 2);
|
||||
REQUIRE( curveSet.getCurve(1).evalCC7(0) == 0.0f );
|
||||
// Can't add an implicit curve after the explicit one
|
||||
curveSet.addCurve(sfz::Curve::buildPredefinedCurve(0));
|
||||
REQUIRE(curveSet.getNumCurves() == 2);
|
||||
curveSet.addCurve(sfz::Curve::buildPredefinedCurve(2), 4);
|
||||
REQUIRE(curveSet.getNumCurves() == 5);
|
||||
REQUIRE( curveSet.getCurve(2).evalCC7(0) == 0.0f ); // Default "empty" curve
|
||||
REQUIRE( curveSet.getCurve(3).evalCC7(0) == 0.0f ); // Default "empty" curve
|
||||
REQUIRE( curveSet.getCurve(4).evalCC7(0) == 1.0f );
|
||||
}
|
||||
|
||||
TEST_CASE("[Curve] Default CurveSet")
|
||||
{
|
||||
auto curveSet = sfz::CurveSet::createPredefined();
|
||||
REQUIRE(curveSet.getNumCurves() == 7);
|
||||
REQUIRE( curveSet.getCurve(0).evalNormalized(0.0f) == 0.0f );
|
||||
REQUIRE( curveSet.getCurve(0).evalNormalized(1.0f) == 1.0f );
|
||||
REQUIRE( curveSet.getCurve(0).evalNormalized(0.3f) == Approx(0.299).margin(1e-3) );
|
||||
|
||||
REQUIRE( curveSet.getCurve(1).evalNormalized(0.0f) == -1.0f );
|
||||
REQUIRE( curveSet.getCurve(1).evalNormalized(1.0f) == 1.0f );
|
||||
REQUIRE( curveSet.getCurve(1).evalNormalized(0.3f) == Approx(-0.402).margin(1e-3) );
|
||||
|
||||
REQUIRE( curveSet.getCurve(2).evalNormalized(0.0f) == 1.0f );
|
||||
REQUIRE( curveSet.getCurve(2).evalNormalized(1.0f) == 0.0f );
|
||||
REQUIRE( curveSet.getCurve(2).evalNormalized(0.3f) == Approx(0.701).margin(1e-3) );
|
||||
|
||||
REQUIRE( curveSet.getCurve(3).evalNormalized(0.0f) == 1.0f );
|
||||
REQUIRE( curveSet.getCurve(3).evalNormalized(1.0f) == -1.0f );
|
||||
REQUIRE( curveSet.getCurve(3).evalNormalized(0.3f) == Approx(0.402).margin(1e-3) );
|
||||
|
||||
REQUIRE( curveSet.getCurve(4).evalNormalized(0.0f) == 0.0f );
|
||||
REQUIRE( curveSet.getCurve(4).evalNormalized(1.0f) == 1.0f );
|
||||
REQUIRE( curveSet.getCurve(4).evalNormalized(0.3f) == Approx(0.09).margin(1e-3) );
|
||||
|
||||
REQUIRE( curveSet.getCurve(5).evalNormalized(0.0f) == 0.0f );
|
||||
REQUIRE( curveSet.getCurve(5).evalNormalized(1.0f) == 1.0f );
|
||||
REQUIRE( curveSet.getCurve(5).evalNormalized(0.3f) == Approx(0.5477).margin(1e-3) );
|
||||
|
||||
REQUIRE( curveSet.getCurve(6).evalNormalized(0.0f) == 1.0f );
|
||||
REQUIRE( curveSet.getCurve(6).evalNormalized(1.0f) == 0.0f );
|
||||
REQUIRE( curveSet.getCurve(6).evalNormalized(0.3f) == Approx(0.837).margin(1e-3) );
|
||||
}
|
||||
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 == "curve")
|
||||
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