Initial commit
This commit is contained in:
commit
6bb58ba419
63 changed files with 3277 additions and 0 deletions
12
.gitmodules
vendored
Normal file
12
.gitmodules
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
[submodule "external/abseil-cpp"]
|
||||||
|
path = external/abseil-cpp
|
||||||
|
url = https://github.com/abseil/abseil-cpp
|
||||||
|
[submodule "external/spdlog"]
|
||||||
|
path = external/spdlog
|
||||||
|
url = https://github.com/gabime/spdlog
|
||||||
|
[submodule "external/Catch2"]
|
||||||
|
path = external/Catch2
|
||||||
|
url = https://github.com/catchorg/Catch2
|
||||||
|
[submodule "external/cxxopts"]
|
||||||
|
path = external/cxxopts
|
||||||
|
url = https://github.com/jarro2783/cxxopts
|
||||||
74
CMakeLists.txt
Normal file
74
CMakeLists.txt
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
cmake_minimum_required(VERSION 3.14)
|
||||||
|
# You need to generate the AppConfig.h and the JuceHeader.h through the Projucer; the rest is more or less here
|
||||||
|
|
||||||
|
project(sfizz VERSION 1.0.0 LANGUAGES CXX)
|
||||||
|
|
||||||
|
# Set the highest possible standard
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
|
||||||
|
# Disable Abseil tests
|
||||||
|
set(BUILD_TESTING OFF)
|
||||||
|
|
||||||
|
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND NOT ANDROID)
|
||||||
|
add_compile_options(-stdlib=libc++)
|
||||||
|
# Presumably need the above for linking too, maybe other options missing as well
|
||||||
|
add_link_options(-stdlib=libc++) # New command on CMake master, not in 3.12 release
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_subdirectory(external/abseil-cpp)
|
||||||
|
add_subdirectory(external/spdlog)
|
||||||
|
add_subdirectory(external/Catch2)
|
||||||
|
add_subdirectory(external/cxxopts)
|
||||||
|
|
||||||
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
|
|
||||||
|
set(COMMON_SOURCES
|
||||||
|
sources/Opcode.cpp
|
||||||
|
sources/Synth.cpp
|
||||||
|
sources/Region.cpp
|
||||||
|
sources/Parser.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
set(TEST_SOURCES
|
||||||
|
sources/Opcode.cpp
|
||||||
|
sources/Synth.cpp
|
||||||
|
sources/Region.cpp
|
||||||
|
sources/Parser.cpp
|
||||||
|
tests/Regex.cpp
|
||||||
|
tests/Helpers.cpp
|
||||||
|
tests/Region.cpp
|
||||||
|
tests/Range.cpp
|
||||||
|
tests/Opcode.cpp
|
||||||
|
tests/Files.cpp
|
||||||
|
tests/Main.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
###############################
|
||||||
|
add_executable(sfzprint sources/ParserMain.cpp ${COMMON_SOURCES})
|
||||||
|
# Per OS properties
|
||||||
|
if(UNIX)
|
||||||
|
target_link_libraries(sfzprint stdc++fs)
|
||||||
|
endif(UNIX)
|
||||||
|
target_link_libraries(sfzprint absl::strings cxxopts)
|
||||||
|
|
||||||
|
|
||||||
|
###############################
|
||||||
|
# Basic command line program
|
||||||
|
add_executable(sfizz sources/Main.cpp ${COMMON_SOURCES})
|
||||||
|
# Per OS properties
|
||||||
|
if(UNIX)
|
||||||
|
target_link_libraries(sfizz stdc++fs)
|
||||||
|
endif(UNIX)
|
||||||
|
target_link_libraries(sfizz absl::strings cxxopts)
|
||||||
|
|
||||||
|
###############################
|
||||||
|
# Test application
|
||||||
|
add_executable(sfizz_tests ${TEST_SOURCES} ${COMMON_SOURCES})
|
||||||
|
# Per OS properties
|
||||||
|
if(UNIX)
|
||||||
|
target_link_libraries(sfizz_tests stdc++fs)
|
||||||
|
endif(UNIX)
|
||||||
|
target_link_libraries(sfizz_tests Catch2::Catch2 absl::strings)
|
||||||
|
target_include_directories(sfizz_tests SYSTEM PRIVATE sources)
|
||||||
|
file(COPY "tests" DESTINATION ${CMAKE_BINARY_DIR})
|
||||||
1
external/Catch2
vendored
Submodule
1
external/Catch2
vendored
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 425957dc63a6dd52234438c6038b901029c71372
|
||||||
1
external/abseil-cpp
vendored
Submodule
1
external/abseil-cpp
vendored
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 36d37ab992038f52276ca66b9da80c1cf0f57dc2
|
||||||
1
external/cxxopts
vendored
Submodule
1
external/cxxopts
vendored
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 3c73d91c0b04e2b59462f0a741be8c07024c1bc0
|
||||||
1
external/spdlog
vendored
Submodule
1
external/spdlog
vendored
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 88b4adebdc0dcb2be9ead196c99db60115e4d307
|
||||||
20
sources/Buffer.h
Normal file
20
sources/Buffer.h
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
#pragma once
|
||||||
|
#include <type_traits>
|
||||||
|
template<class Type>
|
||||||
|
class Buffer
|
||||||
|
{
|
||||||
|
static_assert(std::is_arithmetic<Type>::value, "Type should be arithmetic");
|
||||||
|
public:
|
||||||
|
Buffer()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
~Buffer()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
size_t realSize;
|
||||||
|
size_t alignment;
|
||||||
|
Type* data;
|
||||||
|
};
|
||||||
39
sources/CCMap.h
Normal file
39
sources/CCMap.h
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
namespace sfz
|
||||||
|
{
|
||||||
|
template<class ValueType>
|
||||||
|
class CCMap
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CCMap(const ValueType& defaultValue)
|
||||||
|
: defaultValue(defaultValue) { }
|
||||||
|
|
||||||
|
const ValueType &getWithDefault(int index) const noexcept
|
||||||
|
{
|
||||||
|
auto it = container.find(index);
|
||||||
|
if (it == end(container))
|
||||||
|
{
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ValueType &operator[](const int &key) noexcept
|
||||||
|
{
|
||||||
|
if (!contains(key))
|
||||||
|
container.emplace(key, defaultValue);
|
||||||
|
return container.operator[](key);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool empty() const { return container.empty(); }
|
||||||
|
const ValueType &at(int index) const { return container.at(index); }
|
||||||
|
bool contains(int index) const noexcept { return container.find(index) != end(container); }
|
||||||
|
private:
|
||||||
|
const ValueType defaultValue;
|
||||||
|
std::map<int, ValueType> container;
|
||||||
|
};
|
||||||
|
}
|
||||||
120
sources/Defaults.h
Normal file
120
sources/Defaults.h
Normal file
|
|
@ -0,0 +1,120 @@
|
||||||
|
#pragma once
|
||||||
|
#include "Range.h"
|
||||||
|
#include <limits>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
enum class SfzTrigger { attack, release, release_key, first, legato };
|
||||||
|
enum class SfzLoopMode { no_loop, one_shot, loop_continuous, loop_sustain };
|
||||||
|
enum class SfzOffMode { fast, normal };
|
||||||
|
enum class SfzVelocityOverride { current, previous };
|
||||||
|
enum class SfzCrossfadeCurve { gain, power };
|
||||||
|
|
||||||
|
namespace sfz
|
||||||
|
{
|
||||||
|
namespace Default
|
||||||
|
{
|
||||||
|
// The categories match http://sfzformat.com/
|
||||||
|
// ******* SFZ 1 *******
|
||||||
|
// Sound source: sample playback
|
||||||
|
inline constexpr float delay { 0.0 };
|
||||||
|
inline constexpr float delayRandom { 0.0 };
|
||||||
|
inline constexpr Range<float> delayRange { 0.0, 100.0 };
|
||||||
|
inline constexpr uint32_t offset { 0 };
|
||||||
|
inline constexpr uint32_t offsetRandom { 0 };
|
||||||
|
inline constexpr Range<uint32_t> offsetRange { 0, std::numeric_limits<uint32_t>::max() };
|
||||||
|
inline constexpr Range<uint32_t> sampleEndRange { 0, std::numeric_limits<uint32_t>::max() };
|
||||||
|
inline constexpr Range<uint32_t> sampleCountRange { 0, std::numeric_limits<uint32_t>::max() };
|
||||||
|
inline constexpr SfzLoopMode loopMode { SfzLoopMode::no_loop };
|
||||||
|
inline constexpr Range<uint32_t> loopRange { 0, std::numeric_limits<uint32_t>::max() };
|
||||||
|
|
||||||
|
// Instrument setting: voice lifecycle
|
||||||
|
inline constexpr uint32_t group { 0 };
|
||||||
|
inline constexpr Range<uint32_t> groupRange { 0, std::numeric_limits<uint32_t>::max() };
|
||||||
|
inline constexpr SfzOffMode offMode { SfzOffMode::fast };
|
||||||
|
|
||||||
|
// Region logic: key mapping
|
||||||
|
inline constexpr Range<uint8_t> keyRange { 0, 127 };
|
||||||
|
inline constexpr Range<uint8_t> velocityRange { 0, 127 };
|
||||||
|
|
||||||
|
// Region logic: MIDI conditions
|
||||||
|
inline constexpr Range<uint8_t> channelRange { 1, 16 };
|
||||||
|
inline constexpr Range<uint8_t> ccRange { 0, 127 };
|
||||||
|
inline constexpr uint8_t cc { 0 };
|
||||||
|
inline constexpr Range<int> bendRange { -8192, 8192 };
|
||||||
|
inline constexpr int bend { 0 };
|
||||||
|
inline constexpr SfzVelocityOverride velocityOverride { SfzVelocityOverride::current };
|
||||||
|
|
||||||
|
// Region logic: internal conditions
|
||||||
|
inline constexpr Range<float> randRange { 0.0, 1.0 };
|
||||||
|
inline constexpr Range<uint8_t> aftertouchRange { 0, 127 };
|
||||||
|
inline constexpr uint8_t aftertouch { 0 };
|
||||||
|
inline constexpr Range<float> bpmRange { 0.0, 500.0 };
|
||||||
|
inline constexpr float bpm { 120.0 };
|
||||||
|
inline constexpr uint8_t sequenceLength{ 1 };
|
||||||
|
inline constexpr uint8_t sequencePosition{ 1 };
|
||||||
|
inline constexpr Range<uint8_t> sequenceRange { 1, 100 };
|
||||||
|
|
||||||
|
// Region logic: Triggers
|
||||||
|
inline constexpr SfzTrigger trigger { SfzTrigger::attack };
|
||||||
|
inline constexpr Range<uint8_t> ccTriggerValueRange{ 0, 127 };
|
||||||
|
|
||||||
|
// Performance parameters: amplifier
|
||||||
|
inline constexpr float volume { 0.0 };
|
||||||
|
inline constexpr Range<float> volumeRange { -144.0, 6.0 };
|
||||||
|
inline constexpr Range<float> volumeCCRange { -144.0, 6.0 };
|
||||||
|
inline constexpr float amplitude { 100.0 };
|
||||||
|
inline constexpr Range<float> amplitudeRange { 0.0, 100.0 };
|
||||||
|
inline constexpr float pan { 0.0 };
|
||||||
|
inline constexpr Range<float> panRange { -100.0, 100.0 };
|
||||||
|
inline constexpr Range<float> panCCRange { -200.0, 200.0 };
|
||||||
|
inline constexpr float position { 0.0 };
|
||||||
|
inline constexpr Range<float> positionRange { -100.0, 100.0 };
|
||||||
|
inline constexpr Range<float> positionCCRange { -200.0, 200.0 };
|
||||||
|
inline constexpr float width { 0.0 };
|
||||||
|
inline constexpr Range<float> widthRange { -100.0, 100.0 };
|
||||||
|
inline constexpr Range<float> widthCCRange { -200.0, 200.0 };
|
||||||
|
inline constexpr uint8_t ampKeycenter { 60 };
|
||||||
|
inline constexpr float ampKeytrack { 0.0 };
|
||||||
|
inline constexpr Range<float> ampKeytrackRange { -96, 12 };
|
||||||
|
inline constexpr float ampVeltrack { 100.0 };
|
||||||
|
inline constexpr Range<float> ampVeltrackRange { -100.0, 100.0 };
|
||||||
|
inline constexpr Range<float> ampVelcurveRange { 0.0, 1.0 };
|
||||||
|
inline constexpr float ampRandom { 0.0 };
|
||||||
|
inline constexpr Range<float> ampRandomRange { 0.0, 24.0 };
|
||||||
|
inline constexpr Range<uint8_t> crossfadeKeyInRange { 0, 0 };
|
||||||
|
inline constexpr Range<uint8_t> crossfadeKeyOutRange { 127, 127 };
|
||||||
|
inline constexpr Range<uint8_t> crossfadeVelInRange { 0, 0 };
|
||||||
|
inline constexpr Range<uint8_t> crossfadeVelOutRange { 127, 127 };
|
||||||
|
inline constexpr SfzCrossfadeCurve crossfadeKeyCurve { SfzCrossfadeCurve::power };
|
||||||
|
inline constexpr SfzCrossfadeCurve crossfadeVelCurve { SfzCrossfadeCurve::power };
|
||||||
|
|
||||||
|
// Performance parameters: pitch
|
||||||
|
inline constexpr uint8_t pitchKeycenter { 60 };
|
||||||
|
inline constexpr int pitchKeytrack { 100 };
|
||||||
|
inline constexpr Range<int> pitchKeytrackRange { -1200, 1200 };
|
||||||
|
inline constexpr int pitchRandom { 0 };
|
||||||
|
inline constexpr Range<int> pitchRandomRange { 0, 9600 };
|
||||||
|
inline constexpr int pitchVeltrack { 0 };
|
||||||
|
inline constexpr Range<int> pitchVeltrackRange { -9600, 9600 };
|
||||||
|
inline constexpr int transpose { 0 };
|
||||||
|
inline constexpr Range<int> transposeRange { -127, 127 };
|
||||||
|
inline constexpr int tune { 0 };
|
||||||
|
inline constexpr Range<int> tuneRange { -100, 100 };
|
||||||
|
|
||||||
|
// Envelope generators
|
||||||
|
inline constexpr float attack { 0 };
|
||||||
|
inline constexpr float decay { 0 };
|
||||||
|
inline constexpr float delayEG { 0 };
|
||||||
|
inline constexpr float hold { 0 };
|
||||||
|
inline constexpr float release { 0 };
|
||||||
|
inline constexpr float start { 0.0 };
|
||||||
|
inline constexpr float sustain { 100.0 };
|
||||||
|
inline constexpr float vel2sustain { 0.0 };
|
||||||
|
inline constexpr int depth { 0 };
|
||||||
|
inline constexpr Range<float> egTimeRange { 0.0, 100.0 };
|
||||||
|
inline constexpr Range<float> egPercentRange { 0.0, 100.0 };
|
||||||
|
inline constexpr Range<int> egDepthRange { -12000, 12000 };
|
||||||
|
inline constexpr Range<float> egOnCCTimeRange { -100.0, 100.0 };
|
||||||
|
inline constexpr Range<float> egOnCCPercentRange { -100.0, 100.0 };
|
||||||
|
}
|
||||||
|
}
|
||||||
67
sources/EGDescription.h
Normal file
67
sources/EGDescription.h
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
#pragma once
|
||||||
|
#include "Globals.h"
|
||||||
|
#include "Defaults.h"
|
||||||
|
#include "SfzHelpers.h"
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
|
||||||
|
namespace sfz
|
||||||
|
{
|
||||||
|
|
||||||
|
struct EGDescription
|
||||||
|
{
|
||||||
|
float attack { Default::attack };
|
||||||
|
float decay { Default::decay };
|
||||||
|
float delay { Default::delayEG };
|
||||||
|
float hold { Default::hold };
|
||||||
|
float release { Default::release };
|
||||||
|
float start { Default::start };
|
||||||
|
float sustain { Default::sustain };
|
||||||
|
int depth { Default::depth };
|
||||||
|
float vel2attack { Default::attack };
|
||||||
|
float vel2decay { Default::decay };
|
||||||
|
float vel2delay { Default::delayEG };
|
||||||
|
float vel2hold { Default::hold };
|
||||||
|
float vel2release { Default::release };
|
||||||
|
float vel2sustain { Default::vel2sustain };
|
||||||
|
int vel2depth { Default::depth };
|
||||||
|
|
||||||
|
std::optional<CCValuePair> ccAttack;
|
||||||
|
std::optional<CCValuePair> ccDecay;
|
||||||
|
std::optional<CCValuePair> ccDelay;
|
||||||
|
std::optional<CCValuePair> ccHold;
|
||||||
|
std::optional<CCValuePair> ccRelease;
|
||||||
|
std::optional<CCValuePair> ccStart;
|
||||||
|
std::optional<CCValuePair> ccSustain;
|
||||||
|
|
||||||
|
float getAttack(const CCValueArray &ccValues, uint8_t velocity) const noexcept
|
||||||
|
{
|
||||||
|
return ccSwitchedValue(ccValues, ccAttack, attack) + normalizeCC(velocity)*vel2attack;
|
||||||
|
}
|
||||||
|
float getDecay(const CCValueArray &ccValues, uint8_t velocity) const noexcept
|
||||||
|
{
|
||||||
|
return ccSwitchedValue(ccValues, ccDecay, decay) + normalizeCC(velocity)*vel2decay;
|
||||||
|
}
|
||||||
|
float getDelay(const CCValueArray &ccValues, uint8_t velocity) const noexcept
|
||||||
|
{
|
||||||
|
return ccSwitchedValue(ccValues, ccDelay, delay) + normalizeCC(velocity)*vel2delay;
|
||||||
|
}
|
||||||
|
float getHold(const CCValueArray &ccValues, uint8_t velocity) const noexcept
|
||||||
|
{
|
||||||
|
return ccSwitchedValue(ccValues, ccHold, hold) + normalizeCC(velocity)*vel2hold;
|
||||||
|
}
|
||||||
|
float getRelease(const CCValueArray &ccValues, uint8_t velocity) const noexcept
|
||||||
|
{
|
||||||
|
return ccSwitchedValue(ccValues, ccRelease, release) + normalizeCC(velocity)*vel2release;
|
||||||
|
}
|
||||||
|
float getStart(const CCValueArray &ccValues, uint8_t velocity [[maybe_unused]]) const noexcept
|
||||||
|
{
|
||||||
|
return ccSwitchedValue(ccValues, ccStart, start);
|
||||||
|
}
|
||||||
|
float getSustain(const CCValueArray &ccValues, uint8_t velocity) const noexcept
|
||||||
|
{
|
||||||
|
return ccSwitchedValue(ccValues, ccSustain, sustain) + normalizeCC(velocity)*vel2sustain;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} //namespace sfz
|
||||||
21
sources/Globals.h
Normal file
21
sources/Globals.h
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace sfz
|
||||||
|
{
|
||||||
|
|
||||||
|
namespace Config
|
||||||
|
{
|
||||||
|
inline constexpr double defaultSampleRate { 48000 };
|
||||||
|
inline constexpr int defaultSamplesPerBlock { 1024 };
|
||||||
|
inline constexpr int preloadSize { 32768 };
|
||||||
|
inline constexpr int numChannels { 2 };
|
||||||
|
inline constexpr int numVoices { 64 };
|
||||||
|
inline constexpr int numLoadingThreads { 4 };
|
||||||
|
inline constexpr int centPerSemitone { 100 };
|
||||||
|
inline constexpr float virtuallyZero { 0.00005f };
|
||||||
|
inline constexpr double fastReleaseDuration { 0.01 };
|
||||||
|
inline constexpr char defineCharacter { '$' };
|
||||||
|
inline constexpr int oversamplingFactor { 2 };
|
||||||
|
} // namespace config
|
||||||
|
|
||||||
|
} // namespace sfz
|
||||||
70
sources/Helpers.h
Normal file
70
sources/Helpers.h
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
#pragma once
|
||||||
|
#include <string_view>
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
|
inline void trimInPlace(std::string_view& s)
|
||||||
|
{
|
||||||
|
const auto leftPosition = s.find_first_not_of(" \r\t\n\f\v");
|
||||||
|
if (leftPosition != s.npos)
|
||||||
|
{
|
||||||
|
s.remove_prefix(leftPosition);
|
||||||
|
const auto rightPosition = s.find_last_not_of(" \r\t\n\f\v");
|
||||||
|
s.remove_suffix(s.size() - rightPosition - 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
s.remove_suffix(s.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string_view trim(std::string_view s)
|
||||||
|
{
|
||||||
|
const auto leftPosition = s.find_first_not_of(" \r\t\n\f\v");
|
||||||
|
if (leftPosition != s.npos)
|
||||||
|
{
|
||||||
|
s.remove_prefix(leftPosition);
|
||||||
|
const auto rightPosition = s.find_last_not_of(" \r\t\n\f\v");
|
||||||
|
s.remove_suffix(s.size() - rightPosition - 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
s.remove_suffix(s.size());
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline constexpr unsigned int Fnv1aBasis = 0x811C9DC5;
|
||||||
|
inline constexpr unsigned int Fnv1aPrime = 0x01000193;
|
||||||
|
inline constexpr unsigned int hash(const char *s, unsigned int h = Fnv1aBasis)
|
||||||
|
{
|
||||||
|
return !*s ? h : hash(s + 1, static_cast<unsigned int>((h ^ *s) * static_cast<unsigned long long>(Fnv1aPrime)));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline unsigned int hash(std::string_view s, unsigned int h = Fnv1aBasis)
|
||||||
|
{
|
||||||
|
if (s.length() > 0)
|
||||||
|
return hash(std::string_view(s.data() + 1, s.length() - 1), static_cast<unsigned int>((h ^ s.front()) * static_cast<unsigned long long>(Fnv1aPrime)));
|
||||||
|
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
#if __linux__ || __unix__
|
||||||
|
// These trap into the signal library rather than your own sourcecode
|
||||||
|
// #define ASSERTFALSE { ::kill(0, SIGTRAP); }
|
||||||
|
// #define ASSERTFALSE { raise(SIGTRAP); }
|
||||||
|
#define ASSERTFALSE { __asm__("int3"); }
|
||||||
|
#elif _WIN32 || _WIN64
|
||||||
|
#pragma intrinsic (__debugbreak)
|
||||||
|
#define ASSERTFALSE { __debugbreak(); }
|
||||||
|
#else
|
||||||
|
#define ASSERTFALSE { __asm int 3; }
|
||||||
|
#endif
|
||||||
|
#define ASSERT(expression) if (!(expression)) ASSERTFALSE
|
||||||
|
#include <iostream>
|
||||||
|
#define DBG(ostream) std::cerr << ostream << '\n'
|
||||||
|
#else
|
||||||
|
#define ASSERTFALSE
|
||||||
|
#define ASSERT(expression)
|
||||||
|
#define DBG(ostream)
|
||||||
|
#endif
|
||||||
37
sources/Main.cpp
Normal file
37
sources/Main.cpp
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
#include "Synth.h"
|
||||||
|
#include <iostream>
|
||||||
|
#include "cxxopts.hpp"
|
||||||
|
|
||||||
|
int main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
std::ios::sync_with_stdio(false);
|
||||||
|
std::vector<std::string> filesToParse;
|
||||||
|
cxxopts::Options options("sfzparser", "Parses an sfz file and prints the output");
|
||||||
|
options.add_options()("positional", "SFZ files to parse", cxxopts::value<std::vector<std::string>>(filesToParse));
|
||||||
|
options.parse_positional({"positional"});
|
||||||
|
auto result = options.parse(argc, argv);
|
||||||
|
if (filesToParse.size() == 0)
|
||||||
|
{
|
||||||
|
std::cout << options.help() << '\n';
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sfz::Synth synth;
|
||||||
|
std::filesystem::path filename { filesToParse[0] };
|
||||||
|
synth.loadSfzFile(filename);
|
||||||
|
std::cout << "==========" << '\n';
|
||||||
|
std::cout << "Total:" << '\n';
|
||||||
|
std::cout << "\tMasters: " << synth.getNumMasters() << '\n';
|
||||||
|
std::cout << "\tGroups: " << synth.getNumGroups() << '\n';
|
||||||
|
std::cout << "\tRegions: " << synth.getNumRegions() << '\n';
|
||||||
|
std::cout << "\tCurves: " << synth.getNumCurves() << '\n';
|
||||||
|
std::cout << "==========" << '\n';
|
||||||
|
std::cout << "Included files:" << '\n';
|
||||||
|
for (auto& file: synth.getIncludedFiles())
|
||||||
|
std::cout << '\t' << file.c_str() << '\n';
|
||||||
|
std::cout << "==========" << '\n';
|
||||||
|
std::cout << "Defines:" << '\n';
|
||||||
|
for (auto& define: synth.getDefines())
|
||||||
|
std::cout << '\t' << define.first << '=' << define.second << '\n';
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
19
sources/Opcode.cpp
Normal file
19
sources/Opcode.cpp
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
#include "Opcode.h"
|
||||||
|
|
||||||
|
sfz::Opcode::Opcode(std::string_view inputOpcode, std::string_view inputValue)
|
||||||
|
:opcode(inputOpcode), value(inputValue)
|
||||||
|
{
|
||||||
|
if (const auto lastCharIndex = inputOpcode.find_last_not_of("1234567890"); lastCharIndex != inputOpcode.npos)
|
||||||
|
{
|
||||||
|
int returnedValue;
|
||||||
|
std::string_view parameterView = inputOpcode;
|
||||||
|
parameterView.remove_prefix(lastCharIndex + 1);
|
||||||
|
if (absl::SimpleAtoi(parameterView, &returnedValue))
|
||||||
|
{
|
||||||
|
parameter = returnedValue;
|
||||||
|
opcode.remove_suffix(opcode.size() - lastCharIndex - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
trimInPlace(value);
|
||||||
|
trimInPlace(opcode);
|
||||||
|
}
|
||||||
100
sources/Opcode.h
Normal file
100
sources/Opcode.h
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
#pragma once
|
||||||
|
#include "Helpers.h"
|
||||||
|
#include "SfzHelpers.h"
|
||||||
|
#include "Defaults.h"
|
||||||
|
#include "Range.h"
|
||||||
|
#include <string_view>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
// charconv support is still sketchy with clang/gcc so we use abseil's numbers
|
||||||
|
#include "absl/strings/numbers.h"
|
||||||
|
|
||||||
|
namespace sfz
|
||||||
|
{
|
||||||
|
struct Opcode
|
||||||
|
{
|
||||||
|
Opcode() = delete;
|
||||||
|
Opcode(std::string_view inputOpcode, std::string_view inputValue);
|
||||||
|
std::string_view opcode{};
|
||||||
|
std::string_view value{};
|
||||||
|
// This is to handle the integer parameter of some opcodes
|
||||||
|
std::optional<uint8_t> parameter;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class ValueType>
|
||||||
|
inline std::optional<ValueType> readOpcode(std::string_view value, const Range<ValueType>& validRange)
|
||||||
|
{
|
||||||
|
if constexpr(std::is_integral<ValueType>::value)
|
||||||
|
{
|
||||||
|
int64_t returnedValue;
|
||||||
|
if (!absl::SimpleAtoi(value, &returnedValue))
|
||||||
|
return {};
|
||||||
|
|
||||||
|
if (returnedValue > std::numeric_limits<ValueType>::max())
|
||||||
|
returnedValue = std::numeric_limits<ValueType>::max();
|
||||||
|
if (returnedValue < std::numeric_limits<ValueType>::min())
|
||||||
|
returnedValue = std::numeric_limits<ValueType>::min();
|
||||||
|
|
||||||
|
return validRange.clamp(static_cast<ValueType>(returnedValue));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
float returnedValue;
|
||||||
|
if (!absl::SimpleAtof(value, &returnedValue))
|
||||||
|
return {};
|
||||||
|
|
||||||
|
return validRange.clamp(returnedValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class ValueType>
|
||||||
|
inline void setValueFromOpcode(const Opcode& opcode, ValueType& target, const Range<ValueType>& validRange)
|
||||||
|
{
|
||||||
|
auto value = readOpcode(opcode.value, validRange);
|
||||||
|
if (!value) // Try and read a note rather than a number
|
||||||
|
value = readNoteValue(opcode.value);
|
||||||
|
if (value)
|
||||||
|
target = *value;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class ValueType>
|
||||||
|
inline void setValueFromOpcode(const Opcode& opcode, std::optional<ValueType>& target, const Range<ValueType>& validRange)
|
||||||
|
{
|
||||||
|
auto value = readOpcode(opcode.value, validRange);
|
||||||
|
if (!value) // Try and read a note rather than a number
|
||||||
|
value = readNoteValue(opcode.value);
|
||||||
|
if (value)
|
||||||
|
target = *value;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class ValueType>
|
||||||
|
inline void setRangeEndFromOpcode(const Opcode& opcode, Range<ValueType>& target, const Range<ValueType>& validRange)
|
||||||
|
{
|
||||||
|
auto value = readOpcode(opcode.value, validRange);
|
||||||
|
if (!value) // Try and read a note rather than a number
|
||||||
|
value = readNoteValue(opcode.value);
|
||||||
|
if (value)
|
||||||
|
target.setEnd(*value);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class ValueType>
|
||||||
|
inline void setRangeStartFromOpcode(const Opcode& opcode, Range<ValueType>& target, const Range<ValueType>& validRange)
|
||||||
|
{
|
||||||
|
auto value = readOpcode(opcode.value, validRange);
|
||||||
|
if (!value) // Try and read a note rather than a number
|
||||||
|
value = readNoteValue(opcode.value);
|
||||||
|
if (value)
|
||||||
|
target.setStart(*value);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class ValueType>
|
||||||
|
inline void setCCPairFromOpcode(const Opcode& opcode, std::optional<CCValuePair>& target, const Range<ValueType>& validRange)
|
||||||
|
{
|
||||||
|
auto value = readOpcode(opcode.value, validRange);
|
||||||
|
if (value && opcode.parameter && Default::ccRange.containsWithEnd(*opcode.parameter))
|
||||||
|
target = std::make_pair(*opcode.parameter, *value);
|
||||||
|
else
|
||||||
|
target = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
136
sources/Parser.cpp
Normal file
136
sources/Parser.cpp
Normal file
|
|
@ -0,0 +1,136 @@
|
||||||
|
#include "Parser.h"
|
||||||
|
#include "Helpers.h"
|
||||||
|
#include "Globals.h"
|
||||||
|
#include "absl/strings/str_join.h"
|
||||||
|
#include <fstream>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
using svregex_iterator = std::regex_iterator<std::string_view::const_iterator>;
|
||||||
|
using svmatch_results = std::match_results<std::string_view::const_iterator>;
|
||||||
|
|
||||||
|
void removeCommentOnLine(std::string_view& line)
|
||||||
|
{
|
||||||
|
if (auto position = line.find("//"); position != line.npos)
|
||||||
|
line.remove_suffix(line.size() - position);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sfz::Parser::loadSfzFile(const std::filesystem::path& file)
|
||||||
|
{
|
||||||
|
const auto sfzFile = file.is_absolute() ? file : rootDirectory / file;
|
||||||
|
if (!std::filesystem::exists(sfzFile))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
rootDirectory = file.parent_path();
|
||||||
|
std::vector<std::string> lines;
|
||||||
|
readSfzFile(file, lines);
|
||||||
|
|
||||||
|
aggregatedContent = absl::StrJoin(lines, " ");
|
||||||
|
const std::string_view aggregatedView { aggregatedContent };
|
||||||
|
|
||||||
|
svregex_iterator headerIterator(aggregatedView.cbegin(), aggregatedView.cend(), sfz::Regexes::headers);
|
||||||
|
const auto regexEnd = svregex_iterator();
|
||||||
|
|
||||||
|
std::vector<Opcode> currentMembers;
|
||||||
|
|
||||||
|
for (; headerIterator != regexEnd; ++headerIterator)
|
||||||
|
{
|
||||||
|
svmatch_results headerMatch = *headerIterator;
|
||||||
|
|
||||||
|
// Can't use uniform initialization here because it generates narrowing conversions
|
||||||
|
const std::string_view header(&*headerMatch[1].first, headerMatch[1].length());
|
||||||
|
const std::string_view members(&*headerMatch[2].first, headerMatch[2].length());
|
||||||
|
auto paramIterator = svregex_iterator (members.cbegin(), members.cend(), sfz::Regexes::members);
|
||||||
|
|
||||||
|
// Store or handle members
|
||||||
|
for (; paramIterator != regexEnd; ++paramIterator)
|
||||||
|
{
|
||||||
|
const svmatch_results paramMatch = *paramIterator;
|
||||||
|
const std::string_view opcode(&*paramMatch[1].first, paramMatch[1].length());
|
||||||
|
const std::string_view value(&*paramMatch[2].first, paramMatch[2].length());
|
||||||
|
currentMembers.emplace_back(opcode, value);
|
||||||
|
}
|
||||||
|
callback(header, currentMembers);
|
||||||
|
currentMembers.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sfz::Parser::readSfzFile(const std::filesystem::path& fileName, std::vector<std::string>& lines) noexcept
|
||||||
|
{
|
||||||
|
std::ifstream fileStream(fileName.c_str());
|
||||||
|
if (!fileStream)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// spdlog::info("Including file {}", fileName.string());
|
||||||
|
svmatch_results includeMatch;
|
||||||
|
svmatch_results defineMatch;
|
||||||
|
|
||||||
|
std::string tmpString;
|
||||||
|
while (std::getline(fileStream, tmpString))
|
||||||
|
{
|
||||||
|
std::string_view tmpView { tmpString };
|
||||||
|
|
||||||
|
removeCommentOnLine(tmpView);
|
||||||
|
trimInPlace(tmpView);
|
||||||
|
|
||||||
|
if (tmpView.empty())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// New #include
|
||||||
|
if (std::regex_search(tmpView.begin(), tmpView.end(), includeMatch, sfz::Regexes::includes))
|
||||||
|
{
|
||||||
|
auto includePath = includeMatch.str(1);
|
||||||
|
std::replace(includePath.begin(), includePath.end(), '\\', '/');
|
||||||
|
const auto newFile = rootDirectory / includePath;
|
||||||
|
auto alreadyIncluded = std::find(includedFiles.begin(), includedFiles.end(), newFile);
|
||||||
|
if (std::filesystem::exists(newFile) && alreadyIncluded == includedFiles.end())
|
||||||
|
{
|
||||||
|
includedFiles.push_back(newFile);
|
||||||
|
readSfzFile(newFile, lines);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// New #define
|
||||||
|
if (std::regex_search(tmpView.begin(), tmpView.end(), defineMatch, sfz::Regexes::defines))
|
||||||
|
{
|
||||||
|
defines[defineMatch.str(1)] = defineMatch.str(2);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace defined variables starting with $
|
||||||
|
std::string newString;
|
||||||
|
newString.reserve(tmpView.length());
|
||||||
|
std::string::size_type lastPos = 0;
|
||||||
|
std::string::size_type findPos = tmpView.find(sfz::Config::defineCharacter, lastPos);
|
||||||
|
|
||||||
|
while(findPos < tmpView.npos)
|
||||||
|
{
|
||||||
|
newString.append(tmpView, lastPos, findPos - lastPos);
|
||||||
|
|
||||||
|
for (auto& definePair: defines)
|
||||||
|
{
|
||||||
|
std::string_view candidate = tmpView.substr(findPos, definePair.first.length());
|
||||||
|
if (candidate == definePair.first)
|
||||||
|
{
|
||||||
|
newString += definePair.second;
|
||||||
|
lastPos = findPos + definePair.first.length();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastPos <= findPos)
|
||||||
|
{
|
||||||
|
newString += sfz::Config::defineCharacter;
|
||||||
|
lastPos = findPos + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
findPos = tmpView.find(sfz::Config::defineCharacter, lastPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the rest of the string
|
||||||
|
newString += tmpView.substr(lastPos);
|
||||||
|
lines.push_back(std::move(newString));
|
||||||
|
}
|
||||||
|
}
|
||||||
37
sources/Parser.h
Normal file
37
sources/Parser.h
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
#pragma once
|
||||||
|
#include "Opcode.h"
|
||||||
|
#include <filesystem>
|
||||||
|
#include <regex>
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
namespace sfz
|
||||||
|
{
|
||||||
|
namespace Regexes
|
||||||
|
{
|
||||||
|
inline static std::regex includes { R"V(#include\s*"(.*?)".*$)V", std::regex::optimize };
|
||||||
|
inline static std::regex defines { R"(#define\s*(\$[a-zA-Z0-9]+)\s+([a-zA-Z0-9]+)(?=\s|$))", std::regex::optimize };
|
||||||
|
inline static std::regex headers { R"(<(.*?)>(.*?)(?=<|$))", std::regex::optimize };
|
||||||
|
inline static std::regex members { R"(([a-zA-Z0-9_]+)=([a-zA-Z0-9-_#.\/\s\\\(\),\*]+)(?![a-zA-Z0-9_]*=))", std::regex::optimize };
|
||||||
|
inline static std::regex opcodeParameters{ R"(([a-zA-Z0-9_]+?)([0-9]+)$)", std::regex::optimize };
|
||||||
|
}
|
||||||
|
|
||||||
|
class Parser
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual bool loadSfzFile(const std::filesystem::path& file);
|
||||||
|
const std::map<std::string, std::string>& getDefines() const noexcept { return defines; }
|
||||||
|
const std::vector<std::filesystem::path>& getIncludedFiles() const noexcept { return includedFiles; }
|
||||||
|
protected:
|
||||||
|
virtual void callback(std::string_view header, std::vector<Opcode> members) = 0;
|
||||||
|
std::filesystem::path rootDirectory { std::filesystem::current_path() };
|
||||||
|
private:
|
||||||
|
std::map<std::string, std::string> defines;
|
||||||
|
std::vector<std::filesystem::path> includedFiles;
|
||||||
|
std::string aggregatedContent { };
|
||||||
|
void readSfzFile(const std::filesystem::path& fileName, std::vector<std::string>& lines) noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sfz
|
||||||
77
sources/ParserMain.cpp
Normal file
77
sources/ParserMain.cpp
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
#include "Parser.h"
|
||||||
|
#include "cxxopts.hpp"
|
||||||
|
// #include "spdlog/spdlog.h"
|
||||||
|
#include <iostream>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
|
||||||
|
class PrintingParser: public sfz::Parser
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
int getNumRegions() const noexcept { return numRegions; }
|
||||||
|
int getNumGroups() const noexcept { return numGroups; }
|
||||||
|
int getNumMasters() const noexcept { return numMasters; }
|
||||||
|
int getNumCurves() const noexcept { return numCurves; }
|
||||||
|
protected:
|
||||||
|
void callback(std::string_view header, std::vector<sfz::Opcode> members) override
|
||||||
|
{
|
||||||
|
switch (hash(header))
|
||||||
|
{
|
||||||
|
case hash("master"): numMasters++; break;
|
||||||
|
case hash("group"): numGroups++; break;
|
||||||
|
case hash("region"): numRegions++; break;
|
||||||
|
case hash("curve"): numCurves++; break;
|
||||||
|
}
|
||||||
|
// std::cout << "[" << header << "]" << '\t';
|
||||||
|
// for (auto& member: members)
|
||||||
|
// {
|
||||||
|
// std::cout << member.opcode << "=" << member.value;
|
||||||
|
// if (member.parameter)
|
||||||
|
// std::cout << " (" << (int)*member.parameter << ")";
|
||||||
|
// std::cout << ' ';
|
||||||
|
|
||||||
|
// }
|
||||||
|
// std::cout << '\n';
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
int numRegions { 0 };
|
||||||
|
int numGroups { 0 };
|
||||||
|
int numMasters { 0 };
|
||||||
|
int numCurves { 0 };
|
||||||
|
};
|
||||||
|
|
||||||
|
int main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
std::ios::sync_with_stdio(false);
|
||||||
|
std::vector<std::string> filesToParse;
|
||||||
|
cxxopts::Options options("sfzparser", "Parses an sfz file and prints the output");
|
||||||
|
options.add_options()("positional", "SFZ files to parse", cxxopts::value<std::vector<std::string>>(filesToParse));
|
||||||
|
options.parse_positional({"positional"});
|
||||||
|
auto result = options.parse(argc, argv);
|
||||||
|
if (filesToParse.size() == 0)
|
||||||
|
{
|
||||||
|
std::cout << options.help() << '\n';
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
PrintingParser parser;
|
||||||
|
std::filesystem::path filename { filesToParse[0] };
|
||||||
|
parser.loadSfzFile(filename);
|
||||||
|
std::cout << "==========" << '\n';
|
||||||
|
std::cout << "Total:" << '\n';
|
||||||
|
std::cout << "\tMasters: " << parser.getNumMasters() << '\n';
|
||||||
|
std::cout << "\tGroups: " << parser.getNumGroups() << '\n';
|
||||||
|
std::cout << "\tRegions: " << parser.getNumRegions() << '\n';
|
||||||
|
std::cout << "\tCurves: " << parser.getNumCurves() << '\n';
|
||||||
|
std::cout << "==========" << '\n';
|
||||||
|
std::cout << "Included files:" << '\n';
|
||||||
|
for (auto& file: parser.getIncludedFiles())
|
||||||
|
std::cout << '\t' << file.c_str() << '\n';
|
||||||
|
std::cout << "==========" << '\n';
|
||||||
|
std::cout << "Defines:" << '\n';
|
||||||
|
for (auto& define: parser.getDefines())
|
||||||
|
std::cout << '\t' << define.first << '=' << define.second << '\n';
|
||||||
|
// spdlog::info("Done!");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
75
sources/Range.h
Normal file
75
sources/Range.h
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
#pragma once
|
||||||
|
#include <type_traits>
|
||||||
|
#include <initializer_list>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
template<class Type>
|
||||||
|
class Range
|
||||||
|
{
|
||||||
|
static_assert(std::is_arithmetic<Type>::value, "The Type should be arithmetic");
|
||||||
|
public:
|
||||||
|
constexpr Range() = default;
|
||||||
|
// constexpr Range(std::initializer_list<Type> list)
|
||||||
|
// {
|
||||||
|
// switch(list.size())
|
||||||
|
// {
|
||||||
|
// case 0:
|
||||||
|
// break;
|
||||||
|
// case 1:
|
||||||
|
// _start = *list.begin();
|
||||||
|
// _end = _start;
|
||||||
|
// break;
|
||||||
|
// default:
|
||||||
|
// _start = *list.begin();
|
||||||
|
// _end = *(list.begin() + 1);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
constexpr Range(Type start, Type end) noexcept
|
||||||
|
: _start(start), _end(std::max(start, end)) {}
|
||||||
|
~Range() = default;
|
||||||
|
Type start() const noexcept { return getStart(); }
|
||||||
|
Type getStart() const noexcept { return _start; }
|
||||||
|
Type end() const noexcept { return getEnd(); }
|
||||||
|
Type getEnd() const noexcept { return _end; }
|
||||||
|
std::pair<Type, Type> getPair() const noexcept { return std::make_pair<Type, Type>(_start, _end); }
|
||||||
|
Range(const Range<Type>& range) = default;
|
||||||
|
Range(Range<Type>&& range) = default;
|
||||||
|
void setStart(Type start) noexcept
|
||||||
|
{
|
||||||
|
_start = start;
|
||||||
|
if (start > _end)
|
||||||
|
_end = start;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setEnd(Type end) noexcept
|
||||||
|
{
|
||||||
|
_end = end;
|
||||||
|
if (end < _start)
|
||||||
|
_start = end;
|
||||||
|
}
|
||||||
|
|
||||||
|
Type clamp(Type value) const noexcept { return std::clamp(value, _start, _end); }
|
||||||
|
bool containsWithEnd(Type value) const noexcept { return (value >= _start && value <= _end); }
|
||||||
|
bool contains(Type value) const noexcept { return (value >= _start && value < _end); }
|
||||||
|
private:
|
||||||
|
Type _start { static_cast<Type>(0.0) };
|
||||||
|
Type _end { static_cast<Type>(0.0) };
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class Type>
|
||||||
|
bool operator==(const Range<Type>& lhs, const Range<Type>& rhs)
|
||||||
|
{
|
||||||
|
return (lhs.start() == rhs.start()) && (lhs.end() == rhs.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Type>
|
||||||
|
bool operator==(const Range<Type>& lhs, const std::pair<Type, Type>& rhs)
|
||||||
|
{
|
||||||
|
return (lhs.start() == rhs.first) && (lhs.end() == rhs.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Type>
|
||||||
|
bool operator==(const std::pair<Type, Type>& lhs, const Range<Type>& rhs)
|
||||||
|
{
|
||||||
|
return rhs == lhs;
|
||||||
|
}
|
||||||
243
sources/Region.cpp
Normal file
243
sources/Region.cpp
Normal file
|
|
@ -0,0 +1,243 @@
|
||||||
|
#include "Region.h"
|
||||||
|
#include "Helpers.h"
|
||||||
|
#include "absl/strings/str_replace.h"
|
||||||
|
|
||||||
|
void sfz::Region::parseOpcode(const Opcode& opcode)
|
||||||
|
{
|
||||||
|
switch (hash(opcode.opcode))
|
||||||
|
{
|
||||||
|
// Sound source: sample playback
|
||||||
|
case hash("sample"):
|
||||||
|
sample = absl::StrReplaceAll(trim(opcode.value), {{"\\", "/"}});
|
||||||
|
break;
|
||||||
|
case hash("delay"): setValueFromOpcode(opcode, delay, Default::delayRange); break;
|
||||||
|
case hash("delay_random"): setValueFromOpcode(opcode, delayRandom, Default::delayRange); break;
|
||||||
|
case hash("offset"): setValueFromOpcode(opcode, offset, Default::offsetRange); break;
|
||||||
|
case hash("offset_random"): setValueFromOpcode(opcode, offsetRandom, Default::offsetRange); break;
|
||||||
|
case hash("end"): setValueFromOpcode(opcode, sampleEnd, Default::sampleEndRange); break;
|
||||||
|
case hash("count"): setValueFromOpcode(opcode, sampleCount, Default::sampleCountRange); break;
|
||||||
|
case hash("loopmode"):
|
||||||
|
case hash("loop_mode"):
|
||||||
|
switch(hash(opcode.value))
|
||||||
|
{
|
||||||
|
case hash("no_loop"):
|
||||||
|
loopMode = SfzLoopMode::no_loop;
|
||||||
|
break;
|
||||||
|
case hash("one_shot"):
|
||||||
|
loopMode = SfzLoopMode::one_shot;
|
||||||
|
break;
|
||||||
|
case hash("loop_continuous"):
|
||||||
|
loopMode = SfzLoopMode::loop_continuous;
|
||||||
|
break;
|
||||||
|
case hash("loop_sustain"):
|
||||||
|
loopMode = SfzLoopMode::loop_sustain;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
DBG("Unkown loop mode:" << std::string(opcode.value));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case hash("loopend"):
|
||||||
|
case hash("loop_end"): setRangeEndFromOpcode(opcode, loopRange, Default::loopRange); break;
|
||||||
|
case hash("loopstart"):
|
||||||
|
case hash("loop_start"): setRangeStartFromOpcode(opcode, loopRange, Default::loopRange); break;
|
||||||
|
|
||||||
|
// Instrument settings: voice lifecycle
|
||||||
|
case hash("group"): setValueFromOpcode(opcode, group, Default::groupRange); break;
|
||||||
|
case hash("offby"):
|
||||||
|
case hash("off_by"): setValueFromOpcode(opcode, offBy, Default::groupRange); break;
|
||||||
|
case hash("off_mode"):
|
||||||
|
switch(hash(opcode.value))
|
||||||
|
{
|
||||||
|
case hash("fast"):
|
||||||
|
offMode = SfzOffMode::fast;
|
||||||
|
break;
|
||||||
|
case hash("normal"):
|
||||||
|
offMode = SfzOffMode::normal;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
DBG("Unkown off mode:" << std::string(opcode.value));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
// Region logic: key mapping
|
||||||
|
case hash("lokey"): setRangeStartFromOpcode(opcode, keyRange, Default::keyRange); break;
|
||||||
|
case hash("hikey"): setRangeEndFromOpcode(opcode, keyRange, Default::keyRange); break;
|
||||||
|
case hash("key"):
|
||||||
|
setRangeStartFromOpcode(opcode, keyRange, Default::keyRange);
|
||||||
|
setRangeEndFromOpcode(opcode, keyRange, Default::keyRange);
|
||||||
|
setValueFromOpcode(opcode, pitchKeycenter, Default::keyRange);
|
||||||
|
break;
|
||||||
|
case hash("lovel"): setRangeStartFromOpcode(opcode, velocityRange, Default::velocityRange); break;
|
||||||
|
case hash("hivel"): setRangeEndFromOpcode(opcode, velocityRange, Default::velocityRange); break;
|
||||||
|
|
||||||
|
// Region logic: MIDI conditions
|
||||||
|
case hash("lochan"): setRangeStartFromOpcode(opcode, channelRange, Default::channelRange); break;
|
||||||
|
case hash("hichan"): setRangeEndFromOpcode(opcode, channelRange, Default::channelRange); break;
|
||||||
|
case hash("lobend"): setRangeStartFromOpcode(opcode, bendRange, Default::bendRange); break;
|
||||||
|
case hash("hibend"): setRangeEndFromOpcode(opcode, bendRange, Default::bendRange); break;
|
||||||
|
case hash("locc"):
|
||||||
|
if (opcode.parameter)
|
||||||
|
setRangeStartFromOpcode(opcode, ccConditions[*opcode.parameter], Default::ccRange);
|
||||||
|
break;
|
||||||
|
case hash("hicc"):
|
||||||
|
if (opcode.parameter)
|
||||||
|
setRangeEndFromOpcode(opcode, ccConditions[*opcode.parameter], Default::ccRange);
|
||||||
|
break;
|
||||||
|
case hash("sw_lokey"): setRangeStartFromOpcode(opcode, keyswitchRange, Default::keyRange); break;
|
||||||
|
case hash("sw_hikey"): setRangeEndFromOpcode(opcode, keyswitchRange, Default::keyRange); break;
|
||||||
|
case hash("sw_last"): setValueFromOpcode(opcode, keyswitch, Default::keyRange); break;
|
||||||
|
case hash("sw_down"): setValueFromOpcode(opcode, keyswitchDown, Default::keyRange); break;
|
||||||
|
case hash("sw_up"): setValueFromOpcode(opcode, keyswitchUp, Default::keyRange); break;
|
||||||
|
case hash("sw_previous"): setValueFromOpcode(opcode, previousNote, Default::keyRange); break;
|
||||||
|
case hash("sw_vel"):
|
||||||
|
switch(hash(opcode.value))
|
||||||
|
{
|
||||||
|
case hash("current"):
|
||||||
|
velocityOverride = SfzVelocityOverride::current;
|
||||||
|
break;
|
||||||
|
case hash("previous"):
|
||||||
|
velocityOverride = SfzVelocityOverride::previous;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
DBG("Unknown velocity mode: " << std::string(opcode.value));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
// Region logic: internal conditions
|
||||||
|
case hash("lochanaft"): setRangeStartFromOpcode(opcode, aftertouchRange, Default::aftertouchRange); break;
|
||||||
|
case hash("hichanaft"): setRangeEndFromOpcode(opcode, aftertouchRange, Default::aftertouchRange); break;
|
||||||
|
case hash("lobpm"): setRangeStartFromOpcode(opcode, bpmRange, Default::bpmRange); break;
|
||||||
|
case hash("hibpm"): setRangeEndFromOpcode(opcode, bpmRange, Default::bpmRange); break;
|
||||||
|
case hash("lorand"): setRangeStartFromOpcode(opcode, randRange, Default::randRange); break;
|
||||||
|
case hash("hirand"): setRangeEndFromOpcode(opcode, randRange, Default::randRange); break;
|
||||||
|
case hash("seq_length"): setValueFromOpcode(opcode, sequenceLength, Default::sequenceRange); break;
|
||||||
|
case hash("seq_position"): setValueFromOpcode(opcode, sequencePosition, Default::sequenceRange); break;
|
||||||
|
// Region logic: triggers
|
||||||
|
case hash("trigger"):
|
||||||
|
switch(hash(opcode.value))
|
||||||
|
{
|
||||||
|
case hash("attack"):
|
||||||
|
trigger = SfzTrigger::attack;
|
||||||
|
break;
|
||||||
|
case hash("first"):
|
||||||
|
trigger = SfzTrigger::first;
|
||||||
|
break;
|
||||||
|
case hash("legato"):
|
||||||
|
trigger = SfzTrigger::legato;
|
||||||
|
break;
|
||||||
|
case hash("release"):
|
||||||
|
trigger = SfzTrigger::release;
|
||||||
|
break;
|
||||||
|
case hash("release_key"):
|
||||||
|
trigger = SfzTrigger::release_key;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
DBG("Unknown trigger mode: " << std::string(opcode.value));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case hash("on_locc"):
|
||||||
|
if (opcode.parameter)
|
||||||
|
setRangeStartFromOpcode(opcode, ccTriggers[*opcode.parameter], Default::ccRange);
|
||||||
|
break;
|
||||||
|
case hash("on_hicc"):
|
||||||
|
if (opcode.parameter)
|
||||||
|
setRangeEndFromOpcode(opcode, ccTriggers[*opcode.parameter], Default::ccRange);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Performance parameters: amplifier
|
||||||
|
case hash("volume"): setValueFromOpcode(opcode, volume, Default::volumeRange); break;
|
||||||
|
// case hash("volume_oncc"):
|
||||||
|
// if (opcode.parameter)
|
||||||
|
// setValueFromOpcode(opcode, volumeCCTriggers[*opcode.parameter], Default::volumeCCRange);
|
||||||
|
// break;
|
||||||
|
case hash("amplitude"): setValueFromOpcode(opcode, amplitude, Default::amplitudeRange); break;
|
||||||
|
case hash("amplitude_cc"):
|
||||||
|
case hash("amplitude_oncc"): setCCPairFromOpcode(opcode, amplitudeCC, Default::amplitudeRange); break;
|
||||||
|
case hash("pan"): setValueFromOpcode(opcode, pan, Default::panRange); break;
|
||||||
|
case hash("pan_oncc"): setCCPairFromOpcode(opcode, panCC, Default::panCCRange); break;
|
||||||
|
case hash("position"): setValueFromOpcode(opcode, position, Default::positionRange); break;
|
||||||
|
case hash("position_oncc"): setCCPairFromOpcode(opcode, positionCC, Default::positionCCRange); break;
|
||||||
|
case hash("width"): setValueFromOpcode(opcode, width, Default::widthRange); break;
|
||||||
|
case hash("width_oncc"): setCCPairFromOpcode(opcode, widthCC, Default::widthCCRange); break;
|
||||||
|
case hash("amp_keycenter"): setValueFromOpcode(opcode, ampKeycenter, Default::keyRange); break;
|
||||||
|
case hash("amp_keytrack"): setValueFromOpcode(opcode, ampKeytrack, Default::ampKeytrackRange); break;
|
||||||
|
case hash("amp_veltrack"): setValueFromOpcode(opcode, ampVeltrack, Default::ampVeltrackRange); break;
|
||||||
|
case hash("amp_random"): setValueFromOpcode(opcode, ampRandom, Default::ampRandomRange); break;
|
||||||
|
case hash("amp_velcurve_"):
|
||||||
|
if (opcode.parameter && Default::ccRange.containsWithEnd(*opcode.parameter))
|
||||||
|
{
|
||||||
|
if (auto value = readOpcode(opcode.value, Default::ampVelcurveRange); value)
|
||||||
|
velocityPoints.emplace_back(*opcode.parameter, *value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case hash("xfin_lokey"): setRangeStartFromOpcode(opcode, crossfadeKeyInRange, Default::keyRange); break;
|
||||||
|
case hash("xfin_hikey"): setRangeEndFromOpcode(opcode, crossfadeKeyInRange, Default::keyRange); break;
|
||||||
|
case hash("xfout_lokey"): setRangeStartFromOpcode(opcode, crossfadeKeyOutRange, Default::keyRange); break;
|
||||||
|
case hash("xfout_hikey"): setRangeEndFromOpcode(opcode, crossfadeKeyOutRange, Default::keyRange); break;
|
||||||
|
case hash("xfin_lovel"): setRangeStartFromOpcode(opcode, crossfadeVelInRange, Default::velocityRange); break;
|
||||||
|
case hash("xfin_hivel"): setRangeEndFromOpcode(opcode, crossfadeVelInRange, Default::velocityRange); break;
|
||||||
|
case hash("xfout_lovel"): setRangeStartFromOpcode(opcode, crossfadeVelOutRange, Default::velocityRange); break;
|
||||||
|
case hash("xfout_hivel"): setRangeEndFromOpcode(opcode, crossfadeVelOutRange, Default::velocityRange); break;
|
||||||
|
case hash("xf_keycurve"):
|
||||||
|
switch (hash(opcode.value))
|
||||||
|
{
|
||||||
|
case hash("power"):
|
||||||
|
crossfadeKeyCurve = SfzCrossfadeCurve::power;
|
||||||
|
break;
|
||||||
|
case hash("gain"):
|
||||||
|
crossfadeKeyCurve = SfzCrossfadeCurve::gain;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
DBG("Unknown crossfade power curve: " << std::string(opcode.value));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case hash("xf_velcurve"):
|
||||||
|
switch (hash(opcode.value))
|
||||||
|
{
|
||||||
|
case hash("power"):
|
||||||
|
crossfadeVelCurve = SfzCrossfadeCurve::power;
|
||||||
|
break;
|
||||||
|
case hash("gain"):
|
||||||
|
crossfadeVelCurve = SfzCrossfadeCurve::gain;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
DBG("Unknown crossfade power curve: " << std::string(opcode.value));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Performance parameters: pitch
|
||||||
|
case hash("pitch_keycenter"): setValueFromOpcode(opcode, pitchKeycenter, Default::keyRange); break;
|
||||||
|
case hash("pitch_keytrack"): setValueFromOpcode(opcode, pitchKeytrack, Default::pitchKeytrackRange); break;
|
||||||
|
case hash("pitch_veltrack"): setValueFromOpcode(opcode, pitchVeltrack, Default::pitchVeltrackRange); break;
|
||||||
|
case hash("pitch_random"): setValueFromOpcode(opcode, pitchRandom, Default::pitchRandomRange); break;
|
||||||
|
case hash("transpose"): setValueFromOpcode(opcode, transpose, Default::transposeRange); break;
|
||||||
|
case hash("tune"): setValueFromOpcode(opcode, tune, Default::tuneRange); break;
|
||||||
|
|
||||||
|
// Amplitude Envelope
|
||||||
|
case hash("ampeg_attack"): setValueFromOpcode(opcode, amplitudeEG.attack, Default::egTimeRange); break;
|
||||||
|
case hash("ampeg_decay"): setValueFromOpcode(opcode, amplitudeEG.decay, Default::egTimeRange); break;
|
||||||
|
case hash("ampeg_delay"): setValueFromOpcode(opcode, amplitudeEG.delay, Default::egTimeRange); break;
|
||||||
|
case hash("ampeg_hold"): setValueFromOpcode(opcode, amplitudeEG.hold, Default::egTimeRange); break;
|
||||||
|
case hash("ampeg_release"): setValueFromOpcode(opcode, amplitudeEG.release, Default::egTimeRange); break;
|
||||||
|
case hash("ampeg_start"): setValueFromOpcode(opcode, amplitudeEG.start, Default::egPercentRange); break;
|
||||||
|
case hash("ampeg_sustain"): setValueFromOpcode(opcode, amplitudeEG.sustain, Default::egPercentRange); break;
|
||||||
|
case hash("ampeg_vel2attack"): setValueFromOpcode(opcode, amplitudeEG.vel2attack, Default::egOnCCTimeRange); break;
|
||||||
|
case hash("ampeg_vel2decay"): setValueFromOpcode(opcode, amplitudeEG.vel2decay, Default::egOnCCTimeRange); break;
|
||||||
|
case hash("ampeg_vel2delay"): setValueFromOpcode(opcode, amplitudeEG.vel2delay, Default::egOnCCTimeRange); break;
|
||||||
|
case hash("ampeg_vel2hold"): setValueFromOpcode(opcode, amplitudeEG.vel2hold, Default::egOnCCTimeRange); break;
|
||||||
|
case hash("ampeg_vel2release"): setValueFromOpcode(opcode, amplitudeEG.vel2release, Default::egOnCCTimeRange); break;
|
||||||
|
case hash("ampeg_vel2sustain"): setValueFromOpcode(opcode, amplitudeEG.vel2sustain, Default::egOnCCPercentRange); break;
|
||||||
|
case hash("ampeg_attack_oncc"): setCCPairFromOpcode(opcode, amplitudeEG.ccAttack, Default::egOnCCTimeRange); break;
|
||||||
|
case hash("ampeg_decay_oncc"): setCCPairFromOpcode(opcode, amplitudeEG.ccDecay, Default::egOnCCTimeRange); break;
|
||||||
|
case hash("ampeg_delay_oncc"): setCCPairFromOpcode(opcode, amplitudeEG.ccDelay, Default::egOnCCTimeRange); break;
|
||||||
|
case hash("ampeg_hold_oncc"): setCCPairFromOpcode(opcode, amplitudeEG.ccHold, Default::egOnCCTimeRange); break;
|
||||||
|
case hash("ampeg_release_oncc"): setCCPairFromOpcode(opcode, amplitudeEG.ccRelease, Default::egOnCCTimeRange); break;
|
||||||
|
case hash("ampeg_start_oncc"): setCCPairFromOpcode(opcode, amplitudeEG.ccStart, Default::egOnCCPercentRange); break;
|
||||||
|
case hash("ampeg_sustain_oncc"): setCCPairFromOpcode(opcode, amplitudeEG.ccSustain, Default::egOnCCPercentRange); break;
|
||||||
|
// Ignored opcodes
|
||||||
|
case hash("ampeg_depth"):
|
||||||
|
case hash("ampeg_vel2depth"):
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
std::string opcodeStr { opcode.opcode.begin(), opcode.opcode.end() };
|
||||||
|
unknownOpcodes.push_back(opcodeStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
99
sources/Region.h
Normal file
99
sources/Region.h
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
#pragma once
|
||||||
|
#include <optional>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include "Opcode.h"
|
||||||
|
#include "EGDescription.h"
|
||||||
|
#include "Defaults.h"
|
||||||
|
#include "CCMap.h"
|
||||||
|
namespace sfz
|
||||||
|
{
|
||||||
|
struct Region
|
||||||
|
{
|
||||||
|
void parseOpcode(const Opcode& opcode);
|
||||||
|
// Sound source: sample playback
|
||||||
|
std::string sample {}; // Sample
|
||||||
|
float delay { Default::delay }; // delay
|
||||||
|
float delayRandom { Default::delayRandom }; // delay_random
|
||||||
|
uint32_t offset { Default::offset }; // offset
|
||||||
|
uint32_t offsetRandom { Default::offsetRandom }; // offset_random
|
||||||
|
uint32_t sampleEnd { Default::sampleEndRange.getEnd() }; // end
|
||||||
|
std::optional<uint32_t> sampleCount {}; // count
|
||||||
|
SfzLoopMode loopMode { Default::loopMode }; // loopmode
|
||||||
|
Range<uint32_t> loopRange { Default::loopRange }; //loopstart and loopend
|
||||||
|
|
||||||
|
// Instrument settings: voice lifecycle
|
||||||
|
uint32_t group { Default::group }; // group
|
||||||
|
std::optional<uint32_t> offBy {}; // off_by
|
||||||
|
SfzOffMode offMode { Default::offMode }; // off_mode
|
||||||
|
|
||||||
|
// Region logic: key mapping
|
||||||
|
Range<uint8_t> keyRange{ Default::keyRange }; //lokey, hikey and key
|
||||||
|
Range<uint8_t> velocityRange{ Default::velocityRange }; // hivel and lovel
|
||||||
|
|
||||||
|
// Region logic: MIDI conditions
|
||||||
|
Range<uint8_t> channelRange{ Default::channelRange }; //lochan and hichan
|
||||||
|
Range<int> bendRange{ Default::bendRange }; // hibend and lobend
|
||||||
|
CCMap<Range<uint8_t>> ccConditions { Default::ccRange };
|
||||||
|
Range<uint8_t> keyswitchRange{ Default::keyRange }; // sw_hikey and sw_lokey
|
||||||
|
std::optional<uint8_t> keyswitch {}; // sw_last
|
||||||
|
std::optional<uint8_t> keyswitchUp {}; // sw_up
|
||||||
|
std::optional<uint8_t> keyswitchDown {}; // sw_down
|
||||||
|
std::optional<uint8_t> previousNote {}; // sw_previous
|
||||||
|
SfzVelocityOverride velocityOverride { Default::velocityOverride }; // sw_vel
|
||||||
|
|
||||||
|
// Region logic: internal conditions
|
||||||
|
Range<uint8_t> aftertouchRange{ Default::aftertouchRange }; // hichanaft and lochanaft
|
||||||
|
Range<float> bpmRange{ Default::bpmRange }; // hibpm and lobpm
|
||||||
|
Range<float> randRange{ Default::randRange }; // hirand and lorand
|
||||||
|
uint8_t sequenceLength { Default::sequenceLength }; // seq_length
|
||||||
|
uint8_t sequencePosition { Default::sequencePosition }; // seq_position
|
||||||
|
|
||||||
|
// Region logic: triggers
|
||||||
|
SfzTrigger trigger { Default::trigger }; // trigger
|
||||||
|
std::array<uint8_t, 128> lastNoteVelocities; // Keeps the velocities of the previous note-ons if the region has the trigger release_key
|
||||||
|
CCMap<Range<uint8_t>> ccTriggers { Default::ccTriggerValueRange }; // on_loccN on_hiccN
|
||||||
|
|
||||||
|
// Performance parameters: amplifier
|
||||||
|
float volume { Default::volume }; // volume
|
||||||
|
float amplitude { Default::amplitude }; // amplitude
|
||||||
|
float pan { Default::pan }; // pan
|
||||||
|
float width { Default::width }; // width
|
||||||
|
float position { Default::position }; // position
|
||||||
|
std::optional<CCValuePair> volumeCC; // volume_oncc
|
||||||
|
std::optional<CCValuePair> amplitudeCC; // amplitude_oncc
|
||||||
|
std::optional<CCValuePair> panCC; // pan_oncc
|
||||||
|
std::optional<CCValuePair> widthCC; // width_oncc
|
||||||
|
std::optional<CCValuePair> positionCC; // position_oncc
|
||||||
|
uint8_t ampKeycenter { Default::ampKeycenter }; // amp_keycenter
|
||||||
|
float ampKeytrack { Default::ampKeytrack }; // amp_keytrack
|
||||||
|
float ampVeltrack { Default::ampVeltrack }; // amp_keytrack
|
||||||
|
std::vector<std::pair<int, float>> velocityPoints; // amp_velcurve_N
|
||||||
|
float ampRandom { Default::ampRandom }; // amp_random
|
||||||
|
Range<uint8_t> crossfadeKeyInRange { Default::crossfadeKeyInRange };
|
||||||
|
Range<uint8_t> crossfadeKeyOutRange { Default::crossfadeKeyOutRange };
|
||||||
|
Range<uint8_t> crossfadeVelInRange { Default::crossfadeVelInRange };
|
||||||
|
Range<uint8_t> crossfadeVelOutRange { Default::crossfadeVelOutRange };
|
||||||
|
SfzCrossfadeCurve crossfadeKeyCurve { Default::crossfadeKeyCurve };
|
||||||
|
SfzCrossfadeCurve crossfadeVelCurve { Default::crossfadeVelCurve };
|
||||||
|
|
||||||
|
|
||||||
|
// Performance parameters: pitch
|
||||||
|
uint8_t pitchKeycenter{Default::pitchKeycenter}; // pitch_keycenter
|
||||||
|
int pitchKeytrack{ Default::pitchKeytrack }; // pitch_keytrack
|
||||||
|
int pitchRandom{ Default::pitchRandom }; // pitch_random
|
||||||
|
int pitchVeltrack{ Default::pitchVeltrack }; // pitch_veltrack
|
||||||
|
int transpose { Default::transpose }; // transpose
|
||||||
|
int tune { Default::tune }; // tune
|
||||||
|
|
||||||
|
// Envelopes
|
||||||
|
EGDescription amplitudeEG;
|
||||||
|
EGDescription pitchEG;
|
||||||
|
EGDescription filterEG;
|
||||||
|
|
||||||
|
double sampleRate { Config::defaultSampleRate };
|
||||||
|
int numChannels { 1 };
|
||||||
|
|
||||||
|
std::vector<std::string> unknownOpcodes;
|
||||||
|
};
|
||||||
|
} // namespace sfz
|
||||||
185
sources/SfzHelpers.h
Normal file
185
sources/SfzHelpers.h
Normal file
|
|
@ -0,0 +1,185 @@
|
||||||
|
#pragma once
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <array>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
namespace sfz
|
||||||
|
{
|
||||||
|
|
||||||
|
using CCValueArray = std::array<uint8_t, 128>;
|
||||||
|
using CCValuePair = std::pair<uint8_t, float> ;
|
||||||
|
using CCNamePair = std::pair<uint8_t, std::string>;
|
||||||
|
|
||||||
|
inline std::optional<uint8_t> readNoteValue(const std::string_view&value)
|
||||||
|
{
|
||||||
|
switch(hash(value))
|
||||||
|
{
|
||||||
|
case hash("c-1"): case hash("C-1"): return (uint8_t) 0;
|
||||||
|
case hash("c#-1"): case hash("C#-1"): return (uint8_t) 1;
|
||||||
|
case hash("d-1"): case hash("D-1"): return (uint8_t) 2;
|
||||||
|
case hash("d#-1"): case hash("D#-1"): return (uint8_t) 3;
|
||||||
|
case hash("e-1"): case hash("E-1"): return (uint8_t) 4;
|
||||||
|
case hash("f-1"): case hash("F-1"): return (uint8_t) 5;
|
||||||
|
case hash("f#-1"): case hash("F#-1"): return (uint8_t) 6;
|
||||||
|
case hash("g-1"): case hash("G-1"): return (uint8_t) 7;
|
||||||
|
case hash("g#-1"): case hash("G#-1"): return (uint8_t) 8;
|
||||||
|
case hash("a-1"): case hash("A-1"): return (uint8_t) 9;
|
||||||
|
case hash("a#-1"): case hash("A#-1"): return (uint8_t) 10;
|
||||||
|
case hash("b-1"): case hash("B-1"): return (uint8_t) 11;
|
||||||
|
|
||||||
|
case hash("c0"): case hash("C0"): return (uint8_t) 12;
|
||||||
|
case hash("c#0"): case hash("C#0"): return (uint8_t) 13;
|
||||||
|
case hash("d0"): case hash("D0"): return (uint8_t) 14;
|
||||||
|
case hash("d#0"): case hash("D#0"): return (uint8_t) 15;
|
||||||
|
case hash("e0"): case hash("E0"): return (uint8_t) 16;
|
||||||
|
case hash("f0"): case hash("F0"): return (uint8_t) 17;
|
||||||
|
case hash("f#0"): case hash("F#0"): return (uint8_t) 18;
|
||||||
|
case hash("g0"): case hash("G0"): return (uint8_t) 19;
|
||||||
|
case hash("g#0"): case hash("G#0"): return (uint8_t) 20;
|
||||||
|
case hash("a0"): case hash("A0"): return (uint8_t) 21;
|
||||||
|
case hash("a#0"): case hash("A#0"): return (uint8_t) 22;
|
||||||
|
case hash("b0"): case hash("B0"): return (uint8_t) 23;
|
||||||
|
|
||||||
|
case hash("c1"): case hash("C1"): return (uint8_t) 24;
|
||||||
|
case hash("c#1"): case hash("C#1"): return (uint8_t) 25;
|
||||||
|
case hash("d1"): case hash("D1"): return (uint8_t) 26;
|
||||||
|
case hash("d#1"): case hash("D#1"): return (uint8_t) 27;
|
||||||
|
case hash("e1"): case hash("E1"): return (uint8_t) 28;
|
||||||
|
case hash("f1"): case hash("F1"): return (uint8_t) 29;
|
||||||
|
case hash("f#1"): case hash("F#1"): return (uint8_t) 30;
|
||||||
|
case hash("g1"): case hash("G1"): return (uint8_t) 31;
|
||||||
|
case hash("g#1"): case hash("G#1"): return (uint8_t) 32;
|
||||||
|
case hash("a1"): case hash("A1"): return (uint8_t) 33;
|
||||||
|
case hash("a#1"): case hash("A#1"): return (uint8_t) 34;
|
||||||
|
case hash("b1"): case hash("B1"): return (uint8_t) 35;
|
||||||
|
|
||||||
|
case hash("c2"): case hash("C2"): return (uint8_t) 36;
|
||||||
|
case hash("c#2"): case hash("C#2"): return (uint8_t) 37;
|
||||||
|
case hash("d2"): case hash("D2"): return (uint8_t) 38;
|
||||||
|
case hash("d#2"): case hash("D#2"): return (uint8_t) 39;
|
||||||
|
case hash("e2"): case hash("E2"): return (uint8_t) 40;
|
||||||
|
case hash("f2"): case hash("F2"): return (uint8_t) 41;
|
||||||
|
case hash("f#2"): case hash("F#2"): return (uint8_t) 42;
|
||||||
|
case hash("g2"): case hash("G2"): return (uint8_t) 43;
|
||||||
|
case hash("g#2"): case hash("G#2"): return (uint8_t) 44;
|
||||||
|
case hash("a2"): case hash("A2"): return (uint8_t) 45;
|
||||||
|
case hash("a#2"): case hash("A#2"): return (uint8_t) 46;
|
||||||
|
case hash("b2"): case hash("B2"): return (uint8_t) 47;
|
||||||
|
|
||||||
|
case hash("c3"): case hash("C3"): return (uint8_t) 48;
|
||||||
|
case hash("c#3"): case hash("C#3"): return (uint8_t) 49;
|
||||||
|
case hash("d3"): case hash("D3"): return (uint8_t) 50;
|
||||||
|
case hash("d#3"): case hash("D#3"): return (uint8_t) 51;
|
||||||
|
case hash("e3"): case hash("E3"): return (uint8_t) 52;
|
||||||
|
case hash("f3"): case hash("F3"): return (uint8_t) 53;
|
||||||
|
case hash("f#3"): case hash("F#3"): return (uint8_t) 54;
|
||||||
|
case hash("g3"): case hash("G3"): return (uint8_t) 55;
|
||||||
|
case hash("g#3"): case hash("G#3"): return (uint8_t) 56;
|
||||||
|
case hash("a3"): case hash("A3"): return (uint8_t) 57;
|
||||||
|
case hash("a#3"): case hash("A#3"): return (uint8_t) 58;
|
||||||
|
case hash("b3"): case hash("B3"): return (uint8_t) 59;
|
||||||
|
|
||||||
|
case hash("c4"): case hash("C4"): return (uint8_t) 60;
|
||||||
|
case hash("c#4"): case hash("C#4"): return (uint8_t) 61;
|
||||||
|
case hash("d4"): case hash("D4"): return (uint8_t) 62;
|
||||||
|
case hash("d#4"): case hash("D#4"): return (uint8_t) 63;
|
||||||
|
case hash("e4"): case hash("E4"): return (uint8_t) 64;
|
||||||
|
case hash("f4"): case hash("F4"): return (uint8_t) 65;
|
||||||
|
case hash("f#4"): case hash("F#4"): return (uint8_t) 66;
|
||||||
|
case hash("g4"): case hash("G4"): return (uint8_t) 67;
|
||||||
|
case hash("g#4"): case hash("G#4"): return (uint8_t) 68;
|
||||||
|
case hash("a4"): case hash("A4"): return (uint8_t) 69;
|
||||||
|
case hash("a#4"): case hash("A#4"): return (uint8_t) 70;
|
||||||
|
case hash("b4"): case hash("B4"): return (uint8_t) 71;
|
||||||
|
|
||||||
|
case hash("c5"): case hash("C5"): return (uint8_t) 72;
|
||||||
|
case hash("c#5"): case hash("C#5"): return (uint8_t) 73;
|
||||||
|
case hash("d5"): case hash("D5"): return (uint8_t) 74;
|
||||||
|
case hash("d#5"): case hash("D#5"): return (uint8_t) 75;
|
||||||
|
case hash("e5"): case hash("E5"): return (uint8_t) 76;
|
||||||
|
case hash("f5"): case hash("F5"): return (uint8_t) 77;
|
||||||
|
case hash("f#5"): case hash("F#5"): return (uint8_t) 78;
|
||||||
|
case hash("g5"): case hash("G5"): return (uint8_t) 79;
|
||||||
|
case hash("g#5"): case hash("G#5"): return (uint8_t) 80;
|
||||||
|
case hash("a5"): case hash("A5"): return (uint8_t) 81;
|
||||||
|
case hash("a#5"): case hash("A#5"): return (uint8_t) 82;
|
||||||
|
case hash("b5"): case hash("B5"): return (uint8_t) 83;
|
||||||
|
|
||||||
|
case hash("c6"): case hash("C6"): return (uint8_t) 84;
|
||||||
|
case hash("c#6"): case hash("C#6"): return (uint8_t) 85;
|
||||||
|
case hash("d6"): case hash("D6"): return (uint8_t) 86;
|
||||||
|
case hash("d#6"): case hash("D#6"): return (uint8_t) 87;
|
||||||
|
case hash("e6"): case hash("E6"): return (uint8_t) 88;
|
||||||
|
case hash("f6"): case hash("F6"): return (uint8_t) 89;
|
||||||
|
case hash("f#6"): case hash("F#6"): return (uint8_t) 90;
|
||||||
|
case hash("g6"): case hash("G6"): return (uint8_t) 91;
|
||||||
|
case hash("g#6"): case hash("G#6"): return (uint8_t) 92;
|
||||||
|
case hash("a6"): case hash("A6"): return (uint8_t) 93;
|
||||||
|
case hash("a#6"): case hash("A#6"): return (uint8_t) 94;
|
||||||
|
case hash("b6"): case hash("B6"): return (uint8_t) 95;
|
||||||
|
|
||||||
|
case hash("c7"): case hash("C7"): return (uint8_t) 96;
|
||||||
|
case hash("c#7"): case hash("C#7"): return (uint8_t) 97;
|
||||||
|
case hash("d7"): case hash("D7"): return (uint8_t) 98;
|
||||||
|
case hash("d#7"): case hash("D#7"): return (uint8_t) 99;
|
||||||
|
case hash("e7"): case hash("E7"): return (uint8_t) 100;
|
||||||
|
case hash("f7"): case hash("F7"): return (uint8_t) 101;
|
||||||
|
case hash("f#7"): case hash("F#7"): return (uint8_t) 102;
|
||||||
|
case hash("g7"): case hash("G7"): return (uint8_t) 103;
|
||||||
|
case hash("g#7"): case hash("G#7"): return (uint8_t) 104;
|
||||||
|
case hash("a7"): case hash("A7"): return (uint8_t) 105;
|
||||||
|
case hash("a#7"): case hash("A#7"): return (uint8_t) 106;
|
||||||
|
case hash("b7"): case hash("B7"): return (uint8_t) 107;
|
||||||
|
|
||||||
|
case hash("c8"): case hash("C8"): return (uint8_t) 108;
|
||||||
|
case hash("c#8"): case hash("C#8"): return (uint8_t) 109;
|
||||||
|
case hash("d8"): case hash("D8"): return (uint8_t) 110;
|
||||||
|
case hash("d#8"): case hash("D#8"): return (uint8_t) 111;
|
||||||
|
case hash("e8"): case hash("E8"): return (uint8_t) 112;
|
||||||
|
case hash("f8"): case hash("F8"): return (uint8_t) 113;
|
||||||
|
case hash("f#8"): case hash("F#8"): return (uint8_t) 114;
|
||||||
|
case hash("g8"): case hash("G8"): return (uint8_t) 115;
|
||||||
|
case hash("g#8"): case hash("G#8"): return (uint8_t) 116;
|
||||||
|
case hash("a8"): case hash("A8"): return (uint8_t) 117;
|
||||||
|
case hash("a#8"): case hash("A#8"): return (uint8_t) 118;
|
||||||
|
case hash("b8"): case hash("B8"): return (uint8_t) 119;
|
||||||
|
|
||||||
|
case hash("c9"): case hash("C9"): return (uint8_t) 120;
|
||||||
|
case hash("c#9"): case hash("C#9"): return (uint8_t) 121;
|
||||||
|
case hash("d9"): case hash("D9"): return (uint8_t) 122;
|
||||||
|
case hash("d#9"): case hash("D#9"): return (uint8_t) 123;
|
||||||
|
case hash("e9"): case hash("E9"): return (uint8_t) 124;
|
||||||
|
case hash("f9"): case hash("F9"): return (uint8_t) 125;
|
||||||
|
case hash("f#9"): case hash("F#9"): return (uint8_t) 126;
|
||||||
|
case hash("g9"): case hash("G9"): return (uint8_t) 127;
|
||||||
|
default: return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
inline constexpr float centsFactor(T cents, T centsPerOctave = 1200) { return std::pow(2.0f, static_cast<float>(cents) / centsPerOctave); }
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
inline constexpr float normalizeCC(T ccValue)
|
||||||
|
{
|
||||||
|
static_assert(std::is_integral<T>::value);
|
||||||
|
return static_cast<float>(std::min(std::max(ccValue, static_cast<T>(0)), static_cast<T>(127))) / 127.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
inline constexpr float normalizePercents(T percentValue)
|
||||||
|
{
|
||||||
|
return std::min(std::max(static_cast<float>(percentValue), 0.0f), 100.0f) / 100.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float ccSwitchedValue(const CCValueArray& ccValues, const std::optional<CCValuePair>& ccSwitch, float value) noexcept
|
||||||
|
{
|
||||||
|
if (ccSwitch)
|
||||||
|
return value + ccSwitch->second * normalizeCC(ccValues[ccSwitch->first]);
|
||||||
|
else
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sfz
|
||||||
|
|
||||||
110
sources/Synth.cpp
Normal file
110
sources/Synth.cpp
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
#include "Synth.h"
|
||||||
|
#include "Helpers.h"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
void sfz::Synth::callback(std::string_view header, std::vector<Opcode> members)
|
||||||
|
{
|
||||||
|
switch (hash(header))
|
||||||
|
{
|
||||||
|
case hash("global"):
|
||||||
|
// We shouldn't have multiple global headers in file
|
||||||
|
ASSERT(!hasGlobal);
|
||||||
|
globalOpcodes = std::move(members);
|
||||||
|
hasGlobal = true;
|
||||||
|
break;
|
||||||
|
case hash("control"):
|
||||||
|
// We shouldn't have multiple control headers in file
|
||||||
|
ASSERT(!hasControl)
|
||||||
|
hasControl = true;
|
||||||
|
break;
|
||||||
|
case hash("master"):
|
||||||
|
masterOpcodes = std::move(members);
|
||||||
|
numMasters++;
|
||||||
|
break;
|
||||||
|
case hash("group"):
|
||||||
|
groupOpcodes = std::move(members);
|
||||||
|
numGroups++;
|
||||||
|
break;
|
||||||
|
case hash("region"):
|
||||||
|
buildRegion(members);
|
||||||
|
break;
|
||||||
|
case hash("curve"):
|
||||||
|
// TODO: implement curves
|
||||||
|
numCurves++;
|
||||||
|
break;
|
||||||
|
case hash("effect"):
|
||||||
|
// TODO: implement curves
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
std::cerr << "Unknown header: " << header << '\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sfz::Synth::buildRegion(const std::vector<Opcode>& regionOpcodes)
|
||||||
|
{
|
||||||
|
auto& lastRegion = regions.emplace_back();
|
||||||
|
for (auto& opcode: globalOpcodes)
|
||||||
|
lastRegion.parseOpcode(opcode);
|
||||||
|
for (auto& opcode: masterOpcodes)
|
||||||
|
lastRegion.parseOpcode(opcode);
|
||||||
|
for (auto& opcode: groupOpcodes)
|
||||||
|
lastRegion.parseOpcode(opcode);
|
||||||
|
for (auto& opcode: regionOpcodes)
|
||||||
|
lastRegion.parseOpcode(opcode);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sfz::Synth::clear()
|
||||||
|
{
|
||||||
|
hasGlobal = false;
|
||||||
|
hasControl = false;
|
||||||
|
numGroups = 0;
|
||||||
|
numMasters = 0;
|
||||||
|
numCurves = 0;
|
||||||
|
defaultSwitch = std::nullopt;
|
||||||
|
for (auto& state: ccState)
|
||||||
|
state = 0;
|
||||||
|
ccNames.clear();
|
||||||
|
globalOpcodes.clear();
|
||||||
|
masterOpcodes.clear();
|
||||||
|
groupOpcodes.clear();
|
||||||
|
regions.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void sfz::Synth::handleGlobalOpcodes(const std::vector<Opcode>& members)
|
||||||
|
{
|
||||||
|
for (auto& member: members)
|
||||||
|
{
|
||||||
|
if (member.opcode == "sw_default")
|
||||||
|
setValueFromOpcode(member, defaultSwitch, Default::keyRange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sfz::Synth::handleControlOpcodes(const std::vector<Opcode>& members)
|
||||||
|
{
|
||||||
|
for (auto& member: members)
|
||||||
|
{
|
||||||
|
switch (hash(member.opcode))
|
||||||
|
{
|
||||||
|
case hash("set_cc"):
|
||||||
|
if (member.parameter && Default::ccRange.containsWithEnd(*member.parameter))
|
||||||
|
setValueFromOpcode(member, ccState[*member.parameter], Default::ccRange);
|
||||||
|
break;
|
||||||
|
case hash("label_cc"):
|
||||||
|
if (member.parameter && Default::ccRange.containsWithEnd(*member.parameter))
|
||||||
|
ccNames.emplace_back(*member.parameter, member.value);
|
||||||
|
break;
|
||||||
|
case hash("default_path"):
|
||||||
|
if (auto newPath = std::filesystem::path(member.value); std::filesystem::exists(newPath))
|
||||||
|
rootDirectory = newPath;
|
||||||
|
default:
|
||||||
|
// Unsupported control opcode
|
||||||
|
ASSERTFALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sfz::Synth::loadSfzFile(const std::filesystem::path& filename)
|
||||||
|
{
|
||||||
|
clear();
|
||||||
|
return sfz::Parser::loadSfzFile(filename);
|
||||||
|
}
|
||||||
44
sources/Synth.h
Normal file
44
sources/Synth.h
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
#pragma once
|
||||||
|
#include "Parser.h"
|
||||||
|
#include "Region.h"
|
||||||
|
#include "SfzHelpers.h"
|
||||||
|
#include <vector>
|
||||||
|
#include <optional>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
namespace sfz
|
||||||
|
{
|
||||||
|
|
||||||
|
class Synth: public Parser
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
bool loadSfzFile(const std::filesystem::path& file) override;
|
||||||
|
int getNumRegions() const noexcept { return regions.size(); }
|
||||||
|
int getNumGroups() const noexcept { return numGroups; }
|
||||||
|
int getNumMasters() const noexcept { return numMasters; }
|
||||||
|
int getNumCurves() const noexcept { return numCurves; }
|
||||||
|
const Region* getRegionView(int idx) { return idx < regions.size() ? ®ions[idx] : nullptr; }
|
||||||
|
protected:
|
||||||
|
void callback(std::string_view header, std::vector<Opcode> members) override;
|
||||||
|
private:
|
||||||
|
bool hasGlobal { false };
|
||||||
|
bool hasControl { false };
|
||||||
|
int numGroups { 0 };
|
||||||
|
int numMasters { 0 };
|
||||||
|
int numCurves { 0 };
|
||||||
|
void clear();
|
||||||
|
void handleGlobalOpcodes(const std::vector<Opcode>& members);
|
||||||
|
void handleControlOpcodes(const std::vector<Opcode>& members);
|
||||||
|
std::vector<Opcode> globalOpcodes;
|
||||||
|
std::vector<Opcode> masterOpcodes;
|
||||||
|
std::vector<Opcode> groupOpcodes;
|
||||||
|
|
||||||
|
CCValueArray ccState;
|
||||||
|
std::vector<CCNamePair> ccNames;
|
||||||
|
std::optional<uint8_t> defaultSwitch;
|
||||||
|
|
||||||
|
std::vector<Region> regions;
|
||||||
|
void buildRegion(const std::vector<Opcode>& regionOpcodes);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
10
sources/Voice.h
Normal file
10
sources/Voice.h
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace sfz
|
||||||
|
{
|
||||||
|
class Voice
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
private:
|
||||||
|
};
|
||||||
|
}
|
||||||
276
tests/Files.cpp
Normal file
276
tests/Files.cpp
Normal file
|
|
@ -0,0 +1,276 @@
|
||||||
|
#include "catch2/catch.hpp"
|
||||||
|
#include "../sources/Synth.h"
|
||||||
|
#include <filesystem>
|
||||||
|
using namespace Catch::literals;
|
||||||
|
|
||||||
|
TEST_CASE("[Files] Single region (regions_one.sfz)")
|
||||||
|
{
|
||||||
|
sfz::Synth synth;
|
||||||
|
synth.loadSfzFile(std::filesystem::current_path() / "tests/TestFiles/Regions/regions_one.sfz");
|
||||||
|
REQUIRE( synth.getNumRegions() == 1 );
|
||||||
|
REQUIRE( synth.getRegionView(0)->sample == "dummy.wav" );
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Files] Multiple regions (regions_many.sfz)")
|
||||||
|
{
|
||||||
|
sfz::Synth synth;
|
||||||
|
synth.loadSfzFile(std::filesystem::current_path() / "tests/TestFiles/Regions/regions_many.sfz");
|
||||||
|
REQUIRE( synth.getNumRegions() == 3 );
|
||||||
|
REQUIRE( synth.getRegionView(0)->sample == "dummy.wav" );
|
||||||
|
REQUIRE( synth.getRegionView(1)->sample == "dummy.1.wav" );
|
||||||
|
REQUIRE( synth.getRegionView(2)->sample == "dummy.2.wav" );
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Files] Basic opcodes (regions_opcodes.sfz)")
|
||||||
|
{
|
||||||
|
sfz::Synth synth;
|
||||||
|
synth.loadSfzFile(std::filesystem::current_path() / "tests/TestFiles/Regions/regions_opcodes.sfz");
|
||||||
|
REQUIRE( synth.getNumRegions() == 1 );
|
||||||
|
REQUIRE( synth.getRegionView(0)->channelRange == Range<uint8_t>(2, 14) );
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Files] Underscore opcodes (underscore_opcodes.sfz)")
|
||||||
|
{
|
||||||
|
sfz::Synth synth;
|
||||||
|
synth.loadSfzFile(std::filesystem::current_path() / "tests/TestFiles/Regions/underscore_opcodes.sfz");
|
||||||
|
REQUIRE( synth.getNumRegions() == 1 );
|
||||||
|
REQUIRE( synth.getRegionView(0)->loopMode == SfzLoopMode::loop_sustain );
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Files] Local include")
|
||||||
|
{
|
||||||
|
sfz::Synth synth;
|
||||||
|
synth.loadSfzFile(std::filesystem::current_path() / "tests/TestFiles/Includes/root_local.sfz");
|
||||||
|
REQUIRE( synth.getNumRegions() == 1 );
|
||||||
|
REQUIRE( synth.getRegionView(0)->sample == "dummy.wav" );
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Files] Multiple includes")
|
||||||
|
{
|
||||||
|
sfz::Synth synth;
|
||||||
|
synth.loadSfzFile(std::filesystem::current_path() / "tests/TestFiles/Includes/multiple_includes.sfz");
|
||||||
|
REQUIRE( synth.getNumRegions() == 2 );
|
||||||
|
REQUIRE( synth.getRegionView(0)->sample == "dummy.wav" );
|
||||||
|
REQUIRE( synth.getRegionView(1)->sample == "dummy2.wav" );
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Files] Multiple includes with comments")
|
||||||
|
{
|
||||||
|
sfz::Synth synth;
|
||||||
|
synth.loadSfzFile(std::filesystem::current_path() / "tests/TestFiles/Includes/multiple_includes_with_comments.sfz");
|
||||||
|
REQUIRE( synth.getNumRegions() == 2 );
|
||||||
|
REQUIRE( synth.getRegionView(0)->sample == "dummy.wav" );
|
||||||
|
REQUIRE( synth.getRegionView(1)->sample == "dummy2.wav" );
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Files] Subdir include")
|
||||||
|
{
|
||||||
|
sfz::Synth synth;
|
||||||
|
synth.loadSfzFile(std::filesystem::current_path() / "tests/TestFiles/Includes/root_subdir.sfz");
|
||||||
|
REQUIRE( synth.getNumRegions() == 1 );
|
||||||
|
REQUIRE( synth.getRegionView(0)->sample == "dummy_subdir.wav" );
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Files] Subdir include Win")
|
||||||
|
{
|
||||||
|
sfz::Synth synth;
|
||||||
|
synth.loadSfzFile(std::filesystem::current_path() / "tests/TestFiles/Includes/root_subdir_win.sfz");
|
||||||
|
REQUIRE( synth.getNumRegions() == 1 );
|
||||||
|
REQUIRE( synth.getRegionView(0)->sample == "dummy_subdir.wav" );
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Files] Recursive include")
|
||||||
|
{
|
||||||
|
sfz::Synth synth;
|
||||||
|
synth.loadSfzFile(std::filesystem::current_path() / "tests/TestFiles/Includes/root_recursive.sfz");
|
||||||
|
REQUIRE( synth.getNumRegions() == 2 );
|
||||||
|
REQUIRE( synth.getRegionView(0)->sample == "dummy_recursive2.wav" );
|
||||||
|
REQUIRE( synth.getRegionView(1)->sample == "dummy_recursive1.wav" );
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Files] Include loops")
|
||||||
|
{
|
||||||
|
sfz::Synth synth;
|
||||||
|
synth.loadSfzFile(std::filesystem::current_path() / "tests/TestFiles/Includes/root_loop.sfz");
|
||||||
|
REQUIRE( synth.getNumRegions() == 2 );
|
||||||
|
REQUIRE( synth.getRegionView(0)->sample == "dummy_loop2.wav" );
|
||||||
|
REQUIRE( synth.getRegionView(1)->sample == "dummy_loop1.wav" );
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Files] Define test")
|
||||||
|
{
|
||||||
|
sfz::Synth synth;
|
||||||
|
synth.loadSfzFile(std::filesystem::current_path() / "tests/TestFiles/defines.sfz");
|
||||||
|
REQUIRE( synth.getNumRegions() == 3 );
|
||||||
|
REQUIRE( synth.getRegionView(0)->keyRange == Range<uint8_t>(36, 36) );
|
||||||
|
REQUIRE( synth.getRegionView(1)->keyRange == Range<uint8_t>(38, 38) );
|
||||||
|
REQUIRE( synth.getRegionView(2)->keyRange == Range<uint8_t>(42, 42) );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST_CASE("[Files] Group from AVL")
|
||||||
|
{
|
||||||
|
sfz::Synth synth;
|
||||||
|
synth.loadSfzFile(std::filesystem::current_path() / "tests/TestFiles/groups_avl.sfz");
|
||||||
|
REQUIRE( synth.getNumRegions() == 5 );
|
||||||
|
for (int i = 0; i < synth.getNumRegions(); ++i)
|
||||||
|
{
|
||||||
|
REQUIRE( synth.getRegionView(i)->volume == 6.0f );
|
||||||
|
REQUIRE( synth.getRegionView(i)->keyRange == Range<uint8_t>(36, 36) );
|
||||||
|
}
|
||||||
|
REQUIRE( synth.getRegionView(0)->velocityRange == Range<uint8_t>(1, 26) );
|
||||||
|
REQUIRE( synth.getRegionView(1)->velocityRange == Range<uint8_t>(27, 52) );
|
||||||
|
REQUIRE( synth.getRegionView(2)->velocityRange == Range<uint8_t>(53, 77) );
|
||||||
|
REQUIRE( synth.getRegionView(3)->velocityRange == Range<uint8_t>(78, 102) );
|
||||||
|
REQUIRE( synth.getRegionView(4)->velocityRange == Range<uint8_t>(103, 127) );
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Files] Full hierarchy")
|
||||||
|
{
|
||||||
|
sfz::Synth synth;
|
||||||
|
synth.loadSfzFile(std::filesystem::current_path() / "tests/TestFiles/basic_hierarchy.sfz");
|
||||||
|
REQUIRE( synth.getNumRegions() == 8 );
|
||||||
|
for (int i = 0; i < synth.getNumRegions(); ++i)
|
||||||
|
{
|
||||||
|
REQUIRE( synth.getRegionView(i)->width == 40.0f );
|
||||||
|
}
|
||||||
|
REQUIRE( synth.getRegionView(0)->pan == 30.0f );
|
||||||
|
REQUIRE( synth.getRegionView(0)->delay == 67 );
|
||||||
|
REQUIRE( synth.getRegionView(0)->keyRange == Range<uint8_t>(60, 60) );
|
||||||
|
|
||||||
|
REQUIRE( synth.getRegionView(1)->pan == 30.0f );
|
||||||
|
REQUIRE( synth.getRegionView(1)->delay == 67 );
|
||||||
|
REQUIRE( synth.getRegionView(1)->keyRange == Range<uint8_t>(61, 61) );
|
||||||
|
|
||||||
|
REQUIRE( synth.getRegionView(2)->pan == 30.0f );
|
||||||
|
REQUIRE( synth.getRegionView(2)->delay == 56 );
|
||||||
|
REQUIRE( synth.getRegionView(2)->keyRange == Range<uint8_t>(50, 50) );
|
||||||
|
|
||||||
|
REQUIRE( synth.getRegionView(3)->pan == 30.0f );
|
||||||
|
REQUIRE( synth.getRegionView(3)->delay == 56 );
|
||||||
|
REQUIRE( synth.getRegionView(3)->keyRange == Range<uint8_t>(51, 51) );
|
||||||
|
|
||||||
|
REQUIRE( synth.getRegionView(4)->pan == -10.0f );
|
||||||
|
REQUIRE( synth.getRegionView(4)->delay == 47 );
|
||||||
|
REQUIRE( synth.getRegionView(4)->keyRange == Range<uint8_t>(40, 40) );
|
||||||
|
|
||||||
|
REQUIRE( synth.getRegionView(5)->pan == -10.0f );
|
||||||
|
REQUIRE( synth.getRegionView(5)->delay == 47 );
|
||||||
|
REQUIRE( synth.getRegionView(5)->keyRange == Range<uint8_t>(41, 41) );
|
||||||
|
|
||||||
|
REQUIRE( synth.getRegionView(6)->pan == -10.0f );
|
||||||
|
REQUIRE( synth.getRegionView(6)->delay == 36 );
|
||||||
|
REQUIRE( synth.getRegionView(6)->keyRange == Range<uint8_t>(30, 30) );
|
||||||
|
|
||||||
|
REQUIRE( synth.getRegionView(7)->pan == -10.0f );
|
||||||
|
REQUIRE( synth.getRegionView(7)->delay == 36 );
|
||||||
|
REQUIRE( synth.getRegionView(7)->keyRange == Range<uint8_t>(31, 31) );
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Files] Reloading files")
|
||||||
|
{
|
||||||
|
sfz::Synth synth;
|
||||||
|
synth.loadSfzFile(std::filesystem::current_path() / "tests/TestFiles/basic_hierarchy.sfz");
|
||||||
|
REQUIRE( synth.getNumRegions() == 8 );
|
||||||
|
synth.loadSfzFile(std::filesystem::current_path() / "tests/TestFiles/basic_hierarchy.sfz");
|
||||||
|
REQUIRE( synth.getNumRegions() == 8 );
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Files] Full hierarchy with antislashes")
|
||||||
|
{
|
||||||
|
{
|
||||||
|
sfz::Synth synth;
|
||||||
|
synth.loadSfzFile(std::filesystem::current_path() / "tests/TestFiles/basic_hierarchy.sfz");
|
||||||
|
REQUIRE( synth.getNumRegions() == 8 );
|
||||||
|
REQUIRE( synth.getRegionView(0)->sample == "Regions/dummy.wav" );
|
||||||
|
REQUIRE( synth.getRegionView(1)->sample == "Regions/dummy.1.wav" );
|
||||||
|
REQUIRE( synth.getRegionView(2)->sample == "Regions/dummy.wav" );
|
||||||
|
REQUIRE( synth.getRegionView(3)->sample == "Regions/dummy.1.wav" );
|
||||||
|
REQUIRE( synth.getRegionView(4)->sample == "Regions/dummy.wav" );
|
||||||
|
REQUIRE( synth.getRegionView(5)->sample == "Regions/dummy.1.wav" );
|
||||||
|
REQUIRE( synth.getRegionView(6)->sample == "Regions/dummy.wav" );
|
||||||
|
REQUIRE( synth.getRegionView(7)->sample == "Regions/dummy.1.wav" );
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
sfz::Synth synth;
|
||||||
|
synth.loadSfzFile(std::filesystem::current_path() / "tests/TestFiles/basic_hierarchy_antislash.sfz");
|
||||||
|
REQUIRE( synth.getNumRegions() == 8 );
|
||||||
|
REQUIRE( synth.getRegionView(0)->sample == "Regions/dummy.wav" );
|
||||||
|
REQUIRE( synth.getRegionView(1)->sample == "Regions/dummy.1.wav" );
|
||||||
|
REQUIRE( synth.getRegionView(2)->sample == "Regions/dummy.wav" );
|
||||||
|
REQUIRE( synth.getRegionView(3)->sample == "Regions/dummy.1.wav" );
|
||||||
|
REQUIRE( synth.getRegionView(4)->sample == "Regions/dummy.wav" );
|
||||||
|
REQUIRE( synth.getRegionView(5)->sample == "Regions/dummy.1.wav" );
|
||||||
|
REQUIRE( synth.getRegionView(6)->sample == "Regions/dummy.wav" );
|
||||||
|
REQUIRE( synth.getRegionView(7)->sample == "Regions/dummy.1.wav" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Files] Pizz basic")
|
||||||
|
{
|
||||||
|
sfz::Synth synth;
|
||||||
|
synth.loadSfzFile(std::filesystem::current_path() / "tests/TestFiles/SpecificBugs/MeatBassPizz/Programs/pizz.sfz");
|
||||||
|
REQUIRE( synth.getNumRegions() == 4 );
|
||||||
|
for (int i = 0; i < synth.getNumRegions(); ++i)
|
||||||
|
{
|
||||||
|
REQUIRE( synth.getRegionView(i)->keyRange == Range<uint8_t>(12, 22) );
|
||||||
|
REQUIRE( synth.getRegionView(i)->velocityRange == Range<uint8_t>(97, 127) );
|
||||||
|
REQUIRE( synth.getRegionView(i)->pitchKeycenter == 21 );
|
||||||
|
REQUIRE( synth.getRegionView(i)->ccConditions.getWithDefault(107) == Range<uint8_t>(0, 13) );
|
||||||
|
}
|
||||||
|
REQUIRE( synth.getRegionView(0)->randRange == Range<float>(0, 0.25) );
|
||||||
|
REQUIRE( synth.getRegionView(1)->randRange == Range<float>(0.25, 0.5) );
|
||||||
|
REQUIRE( synth.getRegionView(2)->randRange == Range<float>(0.5, 0.75) );
|
||||||
|
REQUIRE( synth.getRegionView(3)->randRange == Range<float>(0.75, 1.0) );
|
||||||
|
REQUIRE( synth.getRegionView(0)->sample == R"(../Samples/pizz/a0_vl4_rr1.wav)" );
|
||||||
|
REQUIRE( synth.getRegionView(1)->sample == R"(../Samples/pizz/a0_vl4_rr2.wav)" );
|
||||||
|
REQUIRE( synth.getRegionView(2)->sample == R"(../Samples/pizz/a0_vl4_rr3.wav)" );
|
||||||
|
REQUIRE( synth.getRegionView(3)->sample == R"(../Samples/pizz/a0_vl4_rr4.wav)" );
|
||||||
|
}
|
||||||
|
|
||||||
|
// TEST_CASE("[Files] sw_default")
|
||||||
|
// {
|
||||||
|
// const double sampleRate { 48000 };
|
||||||
|
// const int blockSize { 256 };
|
||||||
|
// sfz::Synth synth;
|
||||||
|
// synth.loadSfzFile(std::filesystem::current_path() / "tests/TestFiles/sw_default.sfz");
|
||||||
|
// REQUIRE( synth.getNumRegions() == 4 );
|
||||||
|
// REQUIRE( !synth.getRegionView(0)->isSwitchedOn() );
|
||||||
|
// REQUIRE( synth.getRegionView(1)->isSwitchedOn() );
|
||||||
|
// REQUIRE( !synth.getRegionView(2)->isSwitchedOn() );
|
||||||
|
// REQUIRE( synth.getRegionView(3)->isSwitchedOn() );
|
||||||
|
// }
|
||||||
|
|
||||||
|
// TEST_CASE("[Files] sw_default and playing with switches")
|
||||||
|
// {
|
||||||
|
// const double sampleRate { 48000 };
|
||||||
|
// const int blockSize { 256 };
|
||||||
|
// sfz::Synth synth;
|
||||||
|
// synth.loadSfzFile(std::filesystem::current_path() / "tests/TestFiles/sw_default.sfz");
|
||||||
|
// REQUIRE( synth.getNumRegions() == 4 );
|
||||||
|
// REQUIRE( !synth.getRegionView(0)->isSwitchedOn() );
|
||||||
|
// REQUIRE( synth.getRegionView(1)->isSwitchedOn() );
|
||||||
|
// REQUIRE( !synth.getRegionView(2)->isSwitchedOn() );
|
||||||
|
// REQUIRE( synth.getRegionView(3)->isSwitchedOn() );
|
||||||
|
// AudioBuffer<float> buffer { 2, blockSize };
|
||||||
|
// synth.prepareToPlay(sampleRate, blockSize);
|
||||||
|
// buffer.clear();
|
||||||
|
// synth.registerNoteOn(1, 41, 64, 0);
|
||||||
|
// REQUIRE( synth.getRegionView(0)->isSwitchedOn() );
|
||||||
|
// REQUIRE( !synth.getRegionView(1)->isSwitchedOn() );
|
||||||
|
// REQUIRE( synth.getRegionView(2)->isSwitchedOn() );
|
||||||
|
// REQUIRE( !synth.getRegionView(3)->isSwitchedOn() );
|
||||||
|
// synth.registerNoteOff(1, 41, 0, 0);
|
||||||
|
// synth.registerNoteOn(1, 42, 64, 0);
|
||||||
|
// REQUIRE( !synth.getRegionView(0)->isSwitchedOn() );
|
||||||
|
// REQUIRE( !synth.getRegionView(1)->isSwitchedOn() );
|
||||||
|
// REQUIRE( !synth.getRegionView(2)->isSwitchedOn() );
|
||||||
|
// REQUIRE( !synth.getRegionView(3)->isSwitchedOn() );
|
||||||
|
// synth.registerNoteOff(1, 42, 0, 0);
|
||||||
|
// synth.registerNoteOn(1, 40, 64, 0);
|
||||||
|
// REQUIRE( !synth.getRegionView(0)->isSwitchedOn() );
|
||||||
|
// REQUIRE( synth.getRegionView(1)->isSwitchedOn() );
|
||||||
|
// REQUIRE( !synth.getRegionView(2)->isSwitchedOn() );
|
||||||
|
// REQUIRE( synth.getRegionView(3)->isSwitchedOn() );
|
||||||
|
// }
|
||||||
63
tests/Helpers.cpp
Normal file
63
tests/Helpers.cpp
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
#include "catch2/catch.hpp"
|
||||||
|
#include "../sources/Helpers.h"
|
||||||
|
#include <string_view>
|
||||||
|
using namespace Catch::literals;
|
||||||
|
using namespace std::literals::string_view_literals;
|
||||||
|
|
||||||
|
TEST_CASE("[Helpers] trimInPlace")
|
||||||
|
{
|
||||||
|
SECTION("Trim nothing")
|
||||||
|
{
|
||||||
|
auto input { "view"sv };
|
||||||
|
trimInPlace(input);
|
||||||
|
REQUIRE( input == "view"sv );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Trim spaces")
|
||||||
|
{
|
||||||
|
auto input { " view "sv };
|
||||||
|
trimInPlace(input);
|
||||||
|
REQUIRE( input == "view"sv );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Trim other chars")
|
||||||
|
{
|
||||||
|
auto input { " \tview \t"sv };
|
||||||
|
trimInPlace(input);
|
||||||
|
REQUIRE( input == "view"sv );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Empty view")
|
||||||
|
{
|
||||||
|
auto input { " "sv };
|
||||||
|
trimInPlace(input);
|
||||||
|
REQUIRE( input.empty() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Helpers] trim")
|
||||||
|
{
|
||||||
|
SECTION("Trim nothing")
|
||||||
|
{
|
||||||
|
auto input { "view"sv };
|
||||||
|
REQUIRE( trim(input) == "view"sv );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Trim spaces")
|
||||||
|
{
|
||||||
|
auto input { " view "sv };
|
||||||
|
REQUIRE( trim(input) == "view"sv );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Trim other chars")
|
||||||
|
{
|
||||||
|
auto input { " \tview \t"sv };
|
||||||
|
REQUIRE( trim(input) == "view"sv );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Empty view")
|
||||||
|
{
|
||||||
|
auto input { " "sv };
|
||||||
|
REQUIRE( trim(input).empty() );
|
||||||
|
}
|
||||||
|
}
|
||||||
8
tests/Main.cpp
Normal file
8
tests/Main.cpp
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#define CATCH_CONFIG_MAIN
|
||||||
|
#include "catch2/catch.hpp"
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// int main (int argc, char* argv[])
|
||||||
|
// {
|
||||||
|
// return 0;
|
||||||
|
// }
|
||||||
62
tests/Opcode.cpp
Normal file
62
tests/Opcode.cpp
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
#include "catch2/catch.hpp"
|
||||||
|
#include "../sources/Region.h"
|
||||||
|
using namespace Catch::literals;
|
||||||
|
|
||||||
|
TEST_CASE("[Opcode] Construction")
|
||||||
|
{
|
||||||
|
SECTION("Normal construction")
|
||||||
|
{
|
||||||
|
sfz::Opcode opcode { "sample", "dummy"};
|
||||||
|
REQUIRE( opcode.opcode == "sample" );
|
||||||
|
REQUIRE( opcode.value == "dummy" );
|
||||||
|
REQUIRE( !opcode.parameter );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Normal construction with underscore")
|
||||||
|
{
|
||||||
|
sfz::Opcode opcode { "sample_underscore", "dummy"};
|
||||||
|
REQUIRE( opcode.opcode == "sample_underscore" );
|
||||||
|
REQUIRE( opcode.value == "dummy" );
|
||||||
|
REQUIRE( !opcode.parameter );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Parameterized opcode")
|
||||||
|
{
|
||||||
|
sfz::Opcode opcode { "sample123", "dummy"};
|
||||||
|
REQUIRE( opcode.opcode == "sample" );
|
||||||
|
REQUIRE( opcode.value == "dummy" );
|
||||||
|
REQUIRE( opcode.parameter );
|
||||||
|
REQUIRE( *opcode.parameter == 123 );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Parameterized opcode with underscore")
|
||||||
|
{
|
||||||
|
sfz::Opcode opcode { "sample_underscore123", "dummy"};
|
||||||
|
REQUIRE( opcode.opcode == "sample_underscore" );
|
||||||
|
REQUIRE( opcode.value == "dummy" );
|
||||||
|
REQUIRE( opcode.parameter );
|
||||||
|
REQUIRE( *opcode.parameter == 123 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Opcode] Note values")
|
||||||
|
{
|
||||||
|
auto noteValue = sfz::readNoteValue("c-1");
|
||||||
|
REQUIRE( noteValue );
|
||||||
|
REQUIRE( *noteValue == 0);
|
||||||
|
noteValue = sfz::readNoteValue("C-1");
|
||||||
|
REQUIRE( noteValue );
|
||||||
|
REQUIRE( *noteValue == 0);
|
||||||
|
noteValue = sfz::readNoteValue("g9");
|
||||||
|
REQUIRE( noteValue );
|
||||||
|
REQUIRE( *noteValue == 127);
|
||||||
|
noteValue = sfz::readNoteValue("G9");
|
||||||
|
REQUIRE( noteValue );
|
||||||
|
REQUIRE( *noteValue == 127);
|
||||||
|
noteValue = sfz::readNoteValue("c#4");
|
||||||
|
REQUIRE( noteValue );
|
||||||
|
REQUIRE( *noteValue == 61);
|
||||||
|
noteValue = sfz::readNoteValue("C#4");
|
||||||
|
REQUIRE( noteValue );
|
||||||
|
REQUIRE( *noteValue == 61);
|
||||||
|
}
|
||||||
74
tests/Range.cpp
Normal file
74
tests/Range.cpp
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
#include "Range.h"
|
||||||
|
#include "catch2/catch.hpp"
|
||||||
|
using namespace Catch::literals;
|
||||||
|
|
||||||
|
TEST_CASE("[Range] Equality operators")
|
||||||
|
{
|
||||||
|
Range<int> intRange {1, 1};
|
||||||
|
REQUIRE( intRange == Range<int>(1, 1) );
|
||||||
|
REQUIRE( intRange == std::pair<int, int>(1, 1) );
|
||||||
|
REQUIRE( std::pair<int, int>(1, 1) == intRange );
|
||||||
|
|
||||||
|
Range<float> floatRange {1.0f, 1.0f};
|
||||||
|
REQUIRE( floatRange == Range<float>(1.0f, 1.0f) );
|
||||||
|
REQUIRE( floatRange == std::pair<float, float>(1.0f, 1.0f) );
|
||||||
|
REQUIRE( std::pair<float, float>(1.0f, 1.0f) == floatRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Range] Default ranges for classical types")
|
||||||
|
{
|
||||||
|
Range<int> intRange;
|
||||||
|
REQUIRE( intRange == Range<int>(0, 0) );
|
||||||
|
Range<int32_t> int32_tRange;
|
||||||
|
REQUIRE( int32_tRange == Range<int32_t>(0, 0) );
|
||||||
|
Range<uint32_t> uint32_tRange;
|
||||||
|
REQUIRE( uint32_tRange == Range<uint32_t>(0, 0) );
|
||||||
|
Range<int64_t> int64_tRange;
|
||||||
|
REQUIRE( int64_tRange == Range<int64_t>(0, 0) );
|
||||||
|
Range<uint64_t> uint64_tRange;
|
||||||
|
REQUIRE( uint64_tRange == Range<uint64_t>(0, 0) );
|
||||||
|
Range<float> floatRange;
|
||||||
|
REQUIRE( floatRange == Range<float>(0, 0) );
|
||||||
|
Range<double> doubleRange;
|
||||||
|
REQUIRE( doubleRange == Range<double>(0, 0) );
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Range] Contains")
|
||||||
|
{
|
||||||
|
Range<int> intRange {1, 10};
|
||||||
|
REQUIRE( !intRange.contains(0) );
|
||||||
|
REQUIRE( intRange.contains(1) );
|
||||||
|
REQUIRE( intRange.contains(5) );
|
||||||
|
REQUIRE( !intRange.contains(10) );
|
||||||
|
REQUIRE( !intRange.containsWithEnd(0) );
|
||||||
|
REQUIRE( intRange.containsWithEnd(1) );
|
||||||
|
REQUIRE( intRange.containsWithEnd(5) );
|
||||||
|
REQUIRE( intRange.containsWithEnd(10) );
|
||||||
|
|
||||||
|
Range<float> floatRange {1.0, 10.0};
|
||||||
|
REQUIRE( !floatRange.contains(0.0) );
|
||||||
|
REQUIRE( floatRange.contains(1.0) );
|
||||||
|
REQUIRE( floatRange.contains(5.0) );
|
||||||
|
REQUIRE( !floatRange.contains(10.0) );
|
||||||
|
REQUIRE( !floatRange.containsWithEnd(0.0) );
|
||||||
|
REQUIRE( floatRange.containsWithEnd(1.0) );
|
||||||
|
REQUIRE( floatRange.containsWithEnd(5.0) );
|
||||||
|
REQUIRE( floatRange.containsWithEnd(10.0) );
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Range] Clamp")
|
||||||
|
{
|
||||||
|
Range<int> intRange {1, 10};
|
||||||
|
REQUIRE( intRange.clamp(0) == 1 );
|
||||||
|
REQUIRE( intRange.clamp(1) == 1 );
|
||||||
|
REQUIRE( intRange.clamp(5) == 5 );
|
||||||
|
REQUIRE( intRange.clamp(10) == 10 );
|
||||||
|
REQUIRE( intRange.clamp(11) == 10 );
|
||||||
|
|
||||||
|
Range<float> floatRange {1.0, 10.0};
|
||||||
|
REQUIRE( floatRange.clamp(0.0) == 1.0_a );
|
||||||
|
REQUIRE( floatRange.clamp(1.0) == 1.0_a );
|
||||||
|
REQUIRE( floatRange.clamp(5.0) == 5.0_a );
|
||||||
|
REQUIRE( floatRange.clamp(10.0) == 10.0_a );
|
||||||
|
REQUIRE( floatRange.clamp(11.0) == 10.0_a );
|
||||||
|
}
|
||||||
132
tests/Regex.cpp
Normal file
132
tests/Regex.cpp
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
#include "catch2/catch.hpp"
|
||||||
|
#include "../sources/Parser.h"
|
||||||
|
using namespace Catch::literals;
|
||||||
|
|
||||||
|
void includeTest(const std::string& line, const std::string& fileName)
|
||||||
|
{
|
||||||
|
std::smatch includeMatch;
|
||||||
|
auto found = std::regex_search(line, includeMatch, sfz::Regexes::includes);
|
||||||
|
REQUIRE(found);
|
||||||
|
REQUIRE(includeMatch[1] == fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Regex] #include")
|
||||||
|
{
|
||||||
|
includeTest("#include \"file.sfz\"", "file.sfz");
|
||||||
|
includeTest("#include \"../Programs/file.sfz\"", "../Programs/file.sfz");
|
||||||
|
includeTest("#include \"..\\Programs\\file.sfz\"", "..\\Programs\\file.sfz");
|
||||||
|
includeTest("#include \"file-1.sfz\"", "file-1.sfz");
|
||||||
|
includeTest("#include \"file~1.sfz\"", "file~1.sfz");
|
||||||
|
includeTest("#include \"file_1.sfz\"", "file_1.sfz");
|
||||||
|
includeTest("#include \"file$1.sfz\"", "file$1.sfz");
|
||||||
|
includeTest("#include \"file,1.sfz\"", "file,1.sfz");
|
||||||
|
includeTest("#include \"rubbishCharactersAfter.sfz\" blabldaljf///df", "rubbishCharactersAfter.sfz");
|
||||||
|
includeTest("#include \"lazyMatching.sfz\" b\"", "lazyMatching.sfz");
|
||||||
|
}
|
||||||
|
|
||||||
|
void defineTest(const std::string& line, const std::string& variable, const std::string& value)
|
||||||
|
{
|
||||||
|
std::smatch defineMatch;
|
||||||
|
auto found = std::regex_search(line, defineMatch, sfz::Regexes::defines);
|
||||||
|
REQUIRE(found);
|
||||||
|
REQUIRE(defineMatch[1] == variable);
|
||||||
|
REQUIRE(defineMatch[2] == value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void defineFail(const std::string& line)
|
||||||
|
{
|
||||||
|
std::smatch defineMatch;
|
||||||
|
auto found = std::regex_search(line, defineMatch, sfz::Regexes::defines);
|
||||||
|
REQUIRE(!found);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Regex] #define")
|
||||||
|
{
|
||||||
|
defineTest("#define $number 1", "$number", "1");
|
||||||
|
defineTest("#define $letters QWERasdf", "$letters", "QWERasdf");
|
||||||
|
defineTest("#define $alphanum asr1t44", "$alphanum", "asr1t44");
|
||||||
|
defineTest("#define $whitespace asr1t44 ", "$whitespace", "asr1t44");
|
||||||
|
defineTest("#define $lazyMatching matched bfasd ", "$lazyMatching", "matched");
|
||||||
|
defineFail("#define $symbols# 1");
|
||||||
|
defineFail("#define $symbolsAgain $1");
|
||||||
|
defineFail("#define $trailingSymbols 1$");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Regex] Header")
|
||||||
|
{
|
||||||
|
SECTION("Basic header match")
|
||||||
|
{
|
||||||
|
std::smatch headerMatch;
|
||||||
|
std::string line{"<header>param1=value1 param2=value2<next>"};
|
||||||
|
auto found = std::regex_search(line, headerMatch, sfz::Regexes::headers);
|
||||||
|
REQUIRE(found);
|
||||||
|
REQUIRE(headerMatch[1] == "header");
|
||||||
|
REQUIRE(headerMatch[2] == "param1=value1 param2=value2");
|
||||||
|
}
|
||||||
|
SECTION("EOL header match")
|
||||||
|
{
|
||||||
|
std::smatch headerMatch;
|
||||||
|
std::string line{"<header>param1=value1 param2=value2"};
|
||||||
|
auto found = std::regex_search(line, headerMatch, sfz::Regexes::headers);
|
||||||
|
REQUIRE(found);
|
||||||
|
REQUIRE(headerMatch[1] == "header");
|
||||||
|
REQUIRE(headerMatch[2] == "param1=value1 param2=value2");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void memberTest(const std::string &line, const std::string &variable, const std::string &value)
|
||||||
|
{
|
||||||
|
std::smatch memberMatch;
|
||||||
|
auto found = std::regex_search(line, memberMatch, sfz::Regexes::members);
|
||||||
|
REQUIRE(found);
|
||||||
|
REQUIRE(memberMatch[1] == variable);
|
||||||
|
REQUIRE(memberMatch[2] == value);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Regex] Member")
|
||||||
|
{
|
||||||
|
memberTest("param=value", "param", "value");
|
||||||
|
memberTest("param=113", "param", "113");
|
||||||
|
memberTest("param1=value", "param1", "value");
|
||||||
|
memberTest("param_1=value", "param_1", "value");
|
||||||
|
memberTest("param_1=value", "param_1", "value");
|
||||||
|
memberTest("ampeg_sustain_oncc74=-100", "ampeg_sustain_oncc74", "-100");
|
||||||
|
memberTest("lorand=0.750", "lorand", "0.750");
|
||||||
|
memberTest("sample=value", "sample", "value");
|
||||||
|
memberTest("sample=value-()*", "sample", "value-()*");
|
||||||
|
memberTest("sample=../sample.wav", "sample", "../sample.wav");
|
||||||
|
memberTest("sample=..\\sample.wav", "sample", "..\\sample.wav");
|
||||||
|
memberTest("sample=subdir\\subdir\\sample.wav", "sample", "subdir\\subdir\\sample.wav");
|
||||||
|
memberTest("sample=subdir/subdir/sample.wav", "sample", "subdir/subdir/sample.wav");
|
||||||
|
memberTest("sample=subdir_underscore\\sample.wav", "sample", "subdir_underscore\\sample.wav");
|
||||||
|
memberTest("sample=subdir space\\sample.wav", "sample", "subdir space\\sample.wav");
|
||||||
|
memberTest("sample=subdir space\\sample.wav next_member=value", "sample", "subdir space\\sample.wav");
|
||||||
|
memberTest("sample=..\\Samples\\pizz\\a0_vl3_rr3.wav", "sample", "..\\Samples\\pizz\\a0_vl3_rr3.wav");
|
||||||
|
memberTest("sample=..\\Samples\\SMD Cymbals Stereo (Samples)\\Hi-Hat (Samples)\\01 Hat Tight 1\\RR1\\09_Hat_Tight_Cnt_RR1.wav", "sample", "..\\Samples\\SMD Cymbals Stereo (Samples)\\Hi-Hat (Samples)\\01 Hat Tight 1\\RR1\\09_Hat_Tight_Cnt_RR1.wav");
|
||||||
|
}
|
||||||
|
|
||||||
|
void parameterTest(const std::string& line, const std::string& opcode, const std::string& parameter)
|
||||||
|
{
|
||||||
|
std::smatch parameterMatch;
|
||||||
|
auto found = std::regex_search(line, parameterMatch, sfz::Regexes::opcodeParameters);
|
||||||
|
REQUIRE(found);
|
||||||
|
REQUIRE(parameterMatch[1] == opcode);
|
||||||
|
REQUIRE(parameterMatch[2] == parameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
void parameterFail(const std::string &line)
|
||||||
|
{
|
||||||
|
std::smatch parameterMatch;
|
||||||
|
auto found = std::regex_search(line, parameterMatch, sfz::Regexes::opcodeParameters);
|
||||||
|
REQUIRE(!found);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Regex] Opcode parameter")
|
||||||
|
{
|
||||||
|
parameterTest("opcode_123", "opcode_", "123");
|
||||||
|
parameterTest("xfin_locc1", "xfin_locc", "1");
|
||||||
|
parameterTest("ampeg_hold_oncc24", "ampeg_hold_oncc", "24");
|
||||||
|
parameterTest("lfo02_phase_oncc135", "lfo02_phase_oncc", "135");
|
||||||
|
parameterFail("lfo01_freq");
|
||||||
|
parameterFail("ampeg_sustain");
|
||||||
|
}
|
||||||
940
tests/Region.cpp
Normal file
940
tests/Region.cpp
Normal file
|
|
@ -0,0 +1,940 @@
|
||||||
|
#include "catch2/catch.hpp"
|
||||||
|
#include "../sources/Region.h"
|
||||||
|
using namespace Catch::literals;
|
||||||
|
|
||||||
|
TEST_CASE("[Region] Parsing opcodes")
|
||||||
|
{
|
||||||
|
sfz::Region region;
|
||||||
|
SECTION("sample")
|
||||||
|
{
|
||||||
|
REQUIRE( region.sample == "" );
|
||||||
|
region.parseOpcode({ "sample", "dummy.wav" });
|
||||||
|
REQUIRE( region.sample == "dummy.wav" );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("delay")
|
||||||
|
{
|
||||||
|
REQUIRE( region.delay == 0.0 );
|
||||||
|
region.parseOpcode({ "delay", "1.0" });
|
||||||
|
REQUIRE( region.delay == 1.0 );
|
||||||
|
region.parseOpcode({ "delay", "-1.0" });
|
||||||
|
REQUIRE( region.delay == 0.0 );
|
||||||
|
region.parseOpcode({ "delay", "110.0" });
|
||||||
|
REQUIRE( region.delay == 100.0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("delay_random")
|
||||||
|
{
|
||||||
|
REQUIRE( region.delayRandom == 0.0 );
|
||||||
|
region.parseOpcode({ "delay_random", "1.0" });
|
||||||
|
REQUIRE( region.delayRandom == 1.0);
|
||||||
|
region.parseOpcode({ "delay_random", "-1.0" });
|
||||||
|
REQUIRE( region.delayRandom == 0.0 );
|
||||||
|
region.parseOpcode({ "delay_random", "110.0" });
|
||||||
|
REQUIRE( region.delayRandom == 100.0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("offset")
|
||||||
|
{
|
||||||
|
REQUIRE( region.offset == 0 );
|
||||||
|
region.parseOpcode({ "offset", "1" });
|
||||||
|
REQUIRE( region.offset == 1 );
|
||||||
|
region.parseOpcode({ "offset", "-1" });
|
||||||
|
REQUIRE( region.offset == 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("offset_random")
|
||||||
|
{
|
||||||
|
REQUIRE( region.offsetRandom == 0 );
|
||||||
|
region.parseOpcode({ "offset_random", "1" });
|
||||||
|
REQUIRE( region.offsetRandom == 1 );
|
||||||
|
region.parseOpcode({ "offset_random", "-1" });
|
||||||
|
REQUIRE( region.offsetRandom == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("end")
|
||||||
|
{
|
||||||
|
region.parseOpcode({ "end", "184" });
|
||||||
|
REQUIRE( region.sampleEnd == 184 );
|
||||||
|
region.parseOpcode({ "end", "-1" });
|
||||||
|
REQUIRE( region.sampleEnd == 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("count")
|
||||||
|
{
|
||||||
|
REQUIRE( !region.sampleCount );
|
||||||
|
region.parseOpcode({ "count", "184" });
|
||||||
|
REQUIRE( region.sampleCount );
|
||||||
|
REQUIRE( *region.sampleCount == 184 );
|
||||||
|
region.parseOpcode({ "count", "-1" });
|
||||||
|
REQUIRE( region.sampleCount );
|
||||||
|
REQUIRE( *region.sampleCount == 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("loop_mode")
|
||||||
|
{
|
||||||
|
REQUIRE( region.loopMode == SfzLoopMode::no_loop );
|
||||||
|
region.parseOpcode({ "loop_mode", "no_loop" });
|
||||||
|
REQUIRE( region.loopMode == SfzLoopMode::no_loop );
|
||||||
|
region.parseOpcode({ "loop_mode", "one_shot" });
|
||||||
|
REQUIRE( region.loopMode == SfzLoopMode::one_shot );
|
||||||
|
region.parseOpcode({ "loop_mode", "loop_continuous" });
|
||||||
|
REQUIRE( region.loopMode == SfzLoopMode::loop_continuous );
|
||||||
|
region.parseOpcode({ "loop_mode", "loop_sustain" });
|
||||||
|
REQUIRE( region.loopMode == SfzLoopMode::loop_sustain );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("loopmode")
|
||||||
|
{
|
||||||
|
REQUIRE( region.loopMode == SfzLoopMode::no_loop );
|
||||||
|
region.parseOpcode({ "loopmode", "no_loop" });
|
||||||
|
REQUIRE( region.loopMode == SfzLoopMode::no_loop );
|
||||||
|
region.parseOpcode({ "loopmode", "one_shot" });
|
||||||
|
REQUIRE( region.loopMode == SfzLoopMode::one_shot );
|
||||||
|
region.parseOpcode({ "loopmode", "loop_continuous" });
|
||||||
|
REQUIRE( region.loopMode == SfzLoopMode::loop_continuous );
|
||||||
|
region.parseOpcode({ "loopmode", "loop_sustain" });
|
||||||
|
REQUIRE( region.loopMode == SfzLoopMode::loop_sustain );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("loop_end")
|
||||||
|
{
|
||||||
|
REQUIRE( region.loopRange == Range<uint32_t>(0, 4294967295) );
|
||||||
|
region.parseOpcode({ "loop_end", "184" });
|
||||||
|
REQUIRE( region.loopRange == Range<uint32_t>(0, 184) );
|
||||||
|
region.parseOpcode({ "loop_end", "-1" });
|
||||||
|
REQUIRE( region.loopRange == Range<uint32_t>(0, 0) );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("loop_start")
|
||||||
|
{
|
||||||
|
region.parseOpcode({ "loop_start", "184" });
|
||||||
|
REQUIRE( region.loopRange == Range<uint32_t>(184, 4294967295) );
|
||||||
|
region.parseOpcode({ "loop_start", "-1" });
|
||||||
|
REQUIRE( region.loopRange == Range<uint32_t>(0, 4294967295) );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("loopend")
|
||||||
|
{
|
||||||
|
REQUIRE( region.loopRange == Range<uint32_t>(0, 4294967295) );
|
||||||
|
region.parseOpcode({ "loopend", "184" });
|
||||||
|
REQUIRE( region.loopRange == Range<uint32_t>(0, 184) );
|
||||||
|
region.parseOpcode({ "loopend", "-1" });
|
||||||
|
REQUIRE( region.loopRange == Range<uint32_t>(0, 0) );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("loopstart")
|
||||||
|
{
|
||||||
|
region.parseOpcode({ "loopstart", "184" });
|
||||||
|
REQUIRE( region.loopRange == Range<uint32_t>(184, 4294967295) );
|
||||||
|
region.parseOpcode({ "loopstart", "-1" });
|
||||||
|
REQUIRE( region.loopRange == Range<uint32_t>(0, 4294967295) );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("group")
|
||||||
|
{
|
||||||
|
REQUIRE( region.group == 0 );
|
||||||
|
region.parseOpcode({ "group", "5" });
|
||||||
|
REQUIRE( region.group == 5 );
|
||||||
|
region.parseOpcode({ "group", "-1" });
|
||||||
|
REQUIRE( region.group == 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("off_by")
|
||||||
|
{
|
||||||
|
REQUIRE( !region.offBy );
|
||||||
|
region.parseOpcode({ "off_by", "5" });
|
||||||
|
REQUIRE( region.offBy );
|
||||||
|
REQUIRE( region.offBy == 5 );
|
||||||
|
region.parseOpcode({ "off_by", "-1" });
|
||||||
|
REQUIRE( region.offBy );
|
||||||
|
REQUIRE( region.offBy == 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("off_mode")
|
||||||
|
{
|
||||||
|
REQUIRE( region.offMode == SfzOffMode::fast );
|
||||||
|
region.parseOpcode({ "off_mode", "fast" });
|
||||||
|
REQUIRE( region.offMode == SfzOffMode::fast );
|
||||||
|
region.parseOpcode({ "off_mode", "normal" });
|
||||||
|
REQUIRE( region.offMode == SfzOffMode::normal );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("lokey, hikey, and key")
|
||||||
|
{
|
||||||
|
REQUIRE( region.keyRange == Range<uint8_t>(0, 127) );
|
||||||
|
region.parseOpcode({ "lokey", "37" });
|
||||||
|
REQUIRE( region.keyRange == Range<uint8_t>(37, 127) );
|
||||||
|
region.parseOpcode({ "lokey", "c4" });
|
||||||
|
REQUIRE( region.keyRange == Range<uint8_t>(60, 127) );
|
||||||
|
region.parseOpcode({ "lokey", "128" });
|
||||||
|
REQUIRE( region.keyRange == Range<uint8_t>(127, 127) );
|
||||||
|
region.parseOpcode({ "lokey", "-3" });
|
||||||
|
REQUIRE( region.keyRange == Range<uint8_t>(0, 127) );
|
||||||
|
region.parseOpcode({ "hikey", "65" });
|
||||||
|
REQUIRE( region.keyRange == Range<uint8_t>(0, 65) );
|
||||||
|
region.parseOpcode({ "hikey", "c4" });
|
||||||
|
REQUIRE( region.keyRange == Range<uint8_t>(0, 60) );
|
||||||
|
region.parseOpcode({ "hikey", "-1" });
|
||||||
|
REQUIRE( region.keyRange == Range<uint8_t>(0, 0) );
|
||||||
|
region.parseOpcode({ "hikey", "128" });
|
||||||
|
REQUIRE( region.keyRange == Range<uint8_t>(0, 127) );
|
||||||
|
region.parseOpcode({ "key", "26" });
|
||||||
|
REQUIRE( region.keyRange == Range<uint8_t>(26, 26) );
|
||||||
|
REQUIRE( region.pitchKeycenter == 26 );
|
||||||
|
region.parseOpcode({ "key", "-26" });
|
||||||
|
REQUIRE( region.keyRange == Range<uint8_t>(0, 0) );
|
||||||
|
REQUIRE( region.pitchKeycenter == 0 );
|
||||||
|
region.parseOpcode({ "key", "234" });
|
||||||
|
REQUIRE( region.keyRange == Range<uint8_t>(127, 127) );
|
||||||
|
REQUIRE( region.pitchKeycenter == 127 );
|
||||||
|
region.parseOpcode({ "key", "c4" });
|
||||||
|
REQUIRE( region.keyRange == Range<uint8_t>(60, 60) );
|
||||||
|
REQUIRE( region.pitchKeycenter == 60 );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("lovel, hivel")
|
||||||
|
{
|
||||||
|
REQUIRE( region.velocityRange == Range<uint8_t>(0, 127) );
|
||||||
|
region.parseOpcode({ "lovel", "37" });
|
||||||
|
REQUIRE( region.velocityRange == Range<uint8_t>(37, 127) );
|
||||||
|
region.parseOpcode({ "lovel", "128" });
|
||||||
|
REQUIRE( region.velocityRange == Range<uint8_t>(127, 127) );
|
||||||
|
region.parseOpcode({ "lovel", "-3" });
|
||||||
|
REQUIRE( region.velocityRange == Range<uint8_t>(0, 127) );
|
||||||
|
region.parseOpcode({ "hivel", "65" });
|
||||||
|
REQUIRE( region.velocityRange == Range<uint8_t>(0, 65) );
|
||||||
|
region.parseOpcode({ "hivel", "-1" });
|
||||||
|
REQUIRE( region.velocityRange == Range<uint8_t>(0, 0) );
|
||||||
|
region.parseOpcode({ "hivel", "128" });
|
||||||
|
REQUIRE( region.velocityRange == Range<uint8_t>(0, 127) );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("lochan, hichan")
|
||||||
|
{
|
||||||
|
REQUIRE( region.channelRange == Range<uint8_t>(1, 16) );
|
||||||
|
region.parseOpcode({ "lochan", "4" });
|
||||||
|
REQUIRE( region.channelRange == Range<uint8_t>(4, 16) );
|
||||||
|
region.parseOpcode({ "lochan", "128" });
|
||||||
|
REQUIRE( region.channelRange == Range<uint8_t>(16, 16) );
|
||||||
|
region.parseOpcode({ "lochan", "-3" });
|
||||||
|
REQUIRE( region.channelRange == Range<uint8_t>(1, 16) );
|
||||||
|
region.parseOpcode({ "hichan", "13" });
|
||||||
|
REQUIRE( region.channelRange == Range<uint8_t>(1, 13) );
|
||||||
|
region.parseOpcode({ "hichan", "-1" });
|
||||||
|
REQUIRE( region.channelRange == Range<uint8_t>(1, 1) );
|
||||||
|
region.parseOpcode({ "hichan", "128" });
|
||||||
|
REQUIRE( region.channelRange == Range<uint8_t>(1, 16) );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("lobend, hibend")
|
||||||
|
{
|
||||||
|
REQUIRE( region.bendRange == Range<int>(-8192, 8192) );
|
||||||
|
region.parseOpcode({ "lobend", "4" });
|
||||||
|
REQUIRE( region.bendRange == Range<int>(4, 8192) );
|
||||||
|
region.parseOpcode({ "lobend", "-128" });
|
||||||
|
REQUIRE( region.bendRange == Range<int>(-128, 8192) );
|
||||||
|
region.parseOpcode({ "lobend", "-10000" });
|
||||||
|
REQUIRE( region.bendRange == Range<int>(-8192, 8192) );
|
||||||
|
region.parseOpcode({ "hibend", "13" });
|
||||||
|
REQUIRE( region.bendRange == Range<int>(-8192, 13) );
|
||||||
|
region.parseOpcode({ "hibend", "-1" });
|
||||||
|
REQUIRE( region.bendRange == Range<int>(-8192, -1) );
|
||||||
|
region.parseOpcode({ "hibend", "10000" });
|
||||||
|
REQUIRE( region.bendRange == Range<int>(-8192, 8192) );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("locc, hicc")
|
||||||
|
{
|
||||||
|
REQUIRE( region.ccConditions.getWithDefault(0) == Range<uint8_t>(0, 127) );
|
||||||
|
REQUIRE( region.ccConditions[127] == Range<uint8_t>(0, 127) );
|
||||||
|
region.parseOpcode({ "locc6", "4" });
|
||||||
|
REQUIRE( region.ccConditions[6] == Range<uint8_t>(4, 127) );
|
||||||
|
region.parseOpcode({ "locc12", "-128" });
|
||||||
|
REQUIRE( region.ccConditions[12] == Range<uint8_t>(0, 127) );
|
||||||
|
region.parseOpcode({ "hicc65", "39" });
|
||||||
|
REQUIRE( region.ccConditions[65] == Range<uint8_t>(0, 39) );
|
||||||
|
region.parseOpcode({ "hicc127", "135" });
|
||||||
|
REQUIRE( region.ccConditions[127] == Range<uint8_t>(0, 127) );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("sw_lokey, sw_hikey")
|
||||||
|
{
|
||||||
|
REQUIRE( region.keyswitchRange == Range<uint8_t>(0, 127) );
|
||||||
|
region.parseOpcode({ "sw_lokey", "4" });
|
||||||
|
REQUIRE( region.keyswitchRange == Range<uint8_t>(4, 127) );
|
||||||
|
region.parseOpcode({ "sw_lokey", "128" });
|
||||||
|
REQUIRE( region.keyswitchRange == Range<uint8_t>(127, 127) );
|
||||||
|
region.parseOpcode({ "sw_lokey", "0" });
|
||||||
|
REQUIRE( region.keyswitchRange == Range<uint8_t>(0, 127) );
|
||||||
|
region.parseOpcode({ "sw_hikey", "39" });
|
||||||
|
REQUIRE( region.keyswitchRange == Range<uint8_t>(0, 39) );
|
||||||
|
region.parseOpcode({ "sw_hikey", "135" });
|
||||||
|
REQUIRE( region.keyswitchRange == Range<uint8_t>(0, 127) );
|
||||||
|
region.parseOpcode({ "sw_hikey", "-1" });
|
||||||
|
REQUIRE( region.keyswitchRange == Range<uint8_t>(0, 0) );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("sw_last")
|
||||||
|
{
|
||||||
|
REQUIRE( !region.keyswitch );
|
||||||
|
region.parseOpcode({ "sw_last", "4" });
|
||||||
|
REQUIRE( region.keyswitch );
|
||||||
|
REQUIRE( *region.keyswitch == 4 );
|
||||||
|
region.parseOpcode({ "sw_last", "128" });
|
||||||
|
REQUIRE( region.keyswitch );
|
||||||
|
REQUIRE( *region.keyswitch == 127 );
|
||||||
|
region.parseOpcode({ "sw_last", "-1" });
|
||||||
|
REQUIRE( region.keyswitch );
|
||||||
|
REQUIRE( *region.keyswitch == 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("sw_up")
|
||||||
|
{
|
||||||
|
REQUIRE( !region.keyswitchUp );
|
||||||
|
region.parseOpcode({ "sw_up", "4" });
|
||||||
|
REQUIRE( region.keyswitchUp );
|
||||||
|
REQUIRE( *region.keyswitchUp == 4 );
|
||||||
|
region.parseOpcode({ "sw_up", "128" });
|
||||||
|
REQUIRE( region.keyswitchUp );
|
||||||
|
REQUIRE( *region.keyswitchUp == 127 );
|
||||||
|
region.parseOpcode({ "sw_up", "-1" });
|
||||||
|
REQUIRE( region.keyswitchUp );
|
||||||
|
REQUIRE( *region.keyswitchUp == 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("sw_down")
|
||||||
|
{
|
||||||
|
REQUIRE( !region.keyswitchDown );
|
||||||
|
region.parseOpcode({ "sw_down", "4" });
|
||||||
|
REQUIRE( region.keyswitchDown );
|
||||||
|
REQUIRE( *region.keyswitchDown == 4 );
|
||||||
|
region.parseOpcode({ "sw_down", "128" });
|
||||||
|
REQUIRE( region.keyswitchDown );
|
||||||
|
REQUIRE( *region.keyswitchDown == 127 );
|
||||||
|
region.parseOpcode({ "sw_down", "-1" });
|
||||||
|
REQUIRE( region.keyswitchDown );
|
||||||
|
REQUIRE( *region.keyswitchDown == 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("sw_previous")
|
||||||
|
{
|
||||||
|
REQUIRE( !region.previousNote );
|
||||||
|
region.parseOpcode({ "sw_previous", "4" });
|
||||||
|
REQUIRE( region.previousNote );
|
||||||
|
REQUIRE( *region.previousNote == 4 );
|
||||||
|
region.parseOpcode({ "sw_previous", "128" });
|
||||||
|
REQUIRE( region.previousNote );
|
||||||
|
REQUIRE( *region.previousNote == 127 );
|
||||||
|
region.parseOpcode({ "sw_previous", "-1" });
|
||||||
|
REQUIRE( region.previousNote );
|
||||||
|
REQUIRE( *region.previousNote == 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("sw_vel")
|
||||||
|
{
|
||||||
|
REQUIRE( region.velocityOverride == SfzVelocityOverride::current );
|
||||||
|
region.parseOpcode({ "sw_vel", "current" });
|
||||||
|
REQUIRE( region.velocityOverride == SfzVelocityOverride::current );
|
||||||
|
region.parseOpcode({ "sw_vel", "previous" });
|
||||||
|
REQUIRE( region.velocityOverride == SfzVelocityOverride::previous );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("lochanaft, hichanaft")
|
||||||
|
{
|
||||||
|
REQUIRE( region.aftertouchRange == Range<uint8_t>(0, 127) );
|
||||||
|
region.parseOpcode({ "lochanaft", "4" });
|
||||||
|
REQUIRE( region.aftertouchRange == Range<uint8_t>(4, 127) );
|
||||||
|
region.parseOpcode({ "lochanaft", "128" });
|
||||||
|
REQUIRE( region.aftertouchRange == Range<uint8_t>(127, 127) );
|
||||||
|
region.parseOpcode({ "lochanaft", "0" });
|
||||||
|
REQUIRE( region.aftertouchRange == Range<uint8_t>(0, 127) );
|
||||||
|
region.parseOpcode({ "hichanaft", "39" });
|
||||||
|
REQUIRE( region.aftertouchRange == Range<uint8_t>(0, 39) );
|
||||||
|
region.parseOpcode({ "hichanaft", "135" });
|
||||||
|
REQUIRE( region.aftertouchRange == Range<uint8_t>(0, 127) );
|
||||||
|
region.parseOpcode({ "hichanaft", "-1" });
|
||||||
|
REQUIRE( region.aftertouchRange == Range<uint8_t>(0, 0) );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("lobpm, hibpm")
|
||||||
|
{
|
||||||
|
REQUIRE( region.bpmRange == Range<float>(0, 500) );
|
||||||
|
region.parseOpcode({ "lobpm", "47.5" });
|
||||||
|
REQUIRE( region.bpmRange == Range<float>(47.5, 500) );
|
||||||
|
region.parseOpcode({ "lobpm", "594" });
|
||||||
|
REQUIRE( region.bpmRange == Range<float>(500, 500) );
|
||||||
|
region.parseOpcode({ "lobpm", "0" });
|
||||||
|
REQUIRE( region.bpmRange == Range<float>(0, 500) );
|
||||||
|
region.parseOpcode({ "hibpm", "78" });
|
||||||
|
REQUIRE( region.bpmRange == Range<float>(0, 78) );
|
||||||
|
region.parseOpcode({ "hibpm", "895.4" });
|
||||||
|
REQUIRE( region.bpmRange == Range<float>(0, 500) );
|
||||||
|
region.parseOpcode({ "hibpm", "-1" });
|
||||||
|
REQUIRE( region.bpmRange == Range<float>(0, 0) );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("lorand, hirand")
|
||||||
|
{
|
||||||
|
REQUIRE( region.randRange == Range<float>(0, 1) );
|
||||||
|
region.parseOpcode({ "lorand", "0.5" });
|
||||||
|
REQUIRE( region.randRange == Range<float>(0.5, 1) );
|
||||||
|
region.parseOpcode({ "lorand", "4" });
|
||||||
|
REQUIRE( region.randRange == Range<float>(1, 1) );
|
||||||
|
region.parseOpcode({ "lorand", "0" });
|
||||||
|
REQUIRE( region.randRange == Range<float>(0, 1) );
|
||||||
|
region.parseOpcode({ "hirand", "39" });
|
||||||
|
REQUIRE( region.randRange == Range<float>(0, 1) );
|
||||||
|
region.parseOpcode({ "hirand", "0.7" });
|
||||||
|
REQUIRE( region.randRange == Range<float>(0, 0.7f) );
|
||||||
|
region.parseOpcode({ "hirand", "-1" });
|
||||||
|
REQUIRE( region.randRange == Range<float>(0, 0) );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("seq_length")
|
||||||
|
{
|
||||||
|
REQUIRE( region.sequenceLength == 1 );
|
||||||
|
region.parseOpcode({ "seq_length", "89" });
|
||||||
|
REQUIRE( region.sequenceLength == 89 );
|
||||||
|
region.parseOpcode({ "seq_length", "189" });
|
||||||
|
REQUIRE( region.sequenceLength == 100 );
|
||||||
|
region.parseOpcode({ "seq_length", "-1" });
|
||||||
|
REQUIRE( region.sequenceLength == 1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("seq_position")
|
||||||
|
{
|
||||||
|
REQUIRE( region.sequencePosition == 1 );
|
||||||
|
region.parseOpcode({ "seq_position", "89" });
|
||||||
|
REQUIRE( region.sequencePosition == 89 );
|
||||||
|
region.parseOpcode({ "seq_position", "189" });
|
||||||
|
REQUIRE( region.sequencePosition == 100 );
|
||||||
|
region.parseOpcode({ "seq_position", "-1" });
|
||||||
|
REQUIRE( region.sequencePosition == 1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("trigger")
|
||||||
|
{
|
||||||
|
REQUIRE( region.trigger == SfzTrigger::attack );
|
||||||
|
region.parseOpcode({ "trigger", "attack" });
|
||||||
|
REQUIRE( region.trigger == SfzTrigger::attack );
|
||||||
|
region.parseOpcode({ "trigger", "release" });
|
||||||
|
REQUIRE( region.trigger == SfzTrigger::release );
|
||||||
|
region.parseOpcode({ "trigger", "first" });
|
||||||
|
REQUIRE( region.trigger == SfzTrigger::first );
|
||||||
|
region.parseOpcode({ "trigger", "legato" });
|
||||||
|
REQUIRE( region.trigger == SfzTrigger::legato );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("on_locc, on_hicc")
|
||||||
|
{
|
||||||
|
for (int ccIdx = 1; ccIdx < 128; ++ccIdx)
|
||||||
|
{
|
||||||
|
REQUIRE( !region.ccTriggers.contains(ccIdx) );
|
||||||
|
}
|
||||||
|
region.parseOpcode({ "on_locc45", "15" });
|
||||||
|
REQUIRE( region.ccTriggers.contains(45) );
|
||||||
|
REQUIRE( region.ccTriggers[45] == Range<uint8_t>(15, 127) );
|
||||||
|
region.parseOpcode({ "on_hicc4", "47" });
|
||||||
|
REQUIRE( region.ccTriggers.contains(45) );
|
||||||
|
REQUIRE( region.ccTriggers[4] == Range<uint8_t>(0, 47) );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("volume")
|
||||||
|
{
|
||||||
|
REQUIRE( region.volume == 0.0f );
|
||||||
|
region.parseOpcode({ "volume", "4.2" });
|
||||||
|
REQUIRE( region.volume == 4.2f );
|
||||||
|
region.parseOpcode({ "volume", "-4.2" });
|
||||||
|
REQUIRE( region.volume == -4.2f );
|
||||||
|
region.parseOpcode({ "volume", "-123" });
|
||||||
|
REQUIRE( region.volume == -123.0f );
|
||||||
|
region.parseOpcode({ "volume", "-185" });
|
||||||
|
REQUIRE( region.volume == -144.0f );
|
||||||
|
region.parseOpcode({ "volume", "19" });
|
||||||
|
REQUIRE( region.volume == 6.0f );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("pan")
|
||||||
|
{
|
||||||
|
REQUIRE( region.pan == 0.0f );
|
||||||
|
region.parseOpcode({ "pan", "4.2" });
|
||||||
|
REQUIRE( region.pan == 4.2f );
|
||||||
|
region.parseOpcode({ "pan", "-4.2" });
|
||||||
|
REQUIRE( region.pan == -4.2f );
|
||||||
|
region.parseOpcode({ "pan", "-123" });
|
||||||
|
REQUIRE( region.pan == -100.0f );
|
||||||
|
region.parseOpcode({ "pan", "132" });
|
||||||
|
REQUIRE( region.pan == 100.0f );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("pan_oncc")
|
||||||
|
{
|
||||||
|
REQUIRE( !region.panCC );
|
||||||
|
region.parseOpcode({ "pan_oncc45", "4.2" });
|
||||||
|
REQUIRE( region.panCC );
|
||||||
|
REQUIRE( region.panCC->first == 45 );
|
||||||
|
REQUIRE( region.panCC->second == 4.2f );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("width")
|
||||||
|
{
|
||||||
|
REQUIRE( region.width == 0.0f );
|
||||||
|
region.parseOpcode({ "width", "4.2" });
|
||||||
|
REQUIRE( region.width == 4.2f );
|
||||||
|
region.parseOpcode({ "width", "-4.2" });
|
||||||
|
REQUIRE( region.width == -4.2f );
|
||||||
|
region.parseOpcode({ "width", "-123" });
|
||||||
|
REQUIRE( region.width == -100.0f );
|
||||||
|
region.parseOpcode({ "width", "132" });
|
||||||
|
REQUIRE( region.width == 100.0f );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("width_oncc")
|
||||||
|
{
|
||||||
|
REQUIRE( !region.widthCC );
|
||||||
|
region.parseOpcode({ "width_oncc45", "4.2" });
|
||||||
|
REQUIRE( region.widthCC );
|
||||||
|
REQUIRE( region.widthCC->first == 45 );
|
||||||
|
REQUIRE( region.widthCC->second == 4.2f );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("position")
|
||||||
|
{
|
||||||
|
REQUIRE( region.position == 0.0f );
|
||||||
|
region.parseOpcode({ "position", "4.2" });
|
||||||
|
REQUIRE( region.position == 4.2f );
|
||||||
|
region.parseOpcode({ "position", "-4.2" });
|
||||||
|
REQUIRE( region.position == -4.2f );
|
||||||
|
region.parseOpcode({ "position", "-123" });
|
||||||
|
REQUIRE( region.position == -100.0f );
|
||||||
|
region.parseOpcode({ "position", "132" });
|
||||||
|
REQUIRE( region.position == 100.0f );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("position_oncc")
|
||||||
|
{
|
||||||
|
REQUIRE( !region.positionCC );
|
||||||
|
region.parseOpcode({ "position_oncc45", "4.2" });
|
||||||
|
REQUIRE( region.positionCC );
|
||||||
|
REQUIRE( region.positionCC->first == 45 );
|
||||||
|
REQUIRE( region.positionCC->second == 4.2f );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("amp_keycenter")
|
||||||
|
{
|
||||||
|
REQUIRE( region.ampKeycenter == 60 );
|
||||||
|
region.parseOpcode({ "amp_keycenter", "40" });
|
||||||
|
REQUIRE( region.ampKeycenter == 40 );
|
||||||
|
region.parseOpcode({ "amp_keycenter", "-1" });
|
||||||
|
REQUIRE( region.ampKeycenter == 0 );
|
||||||
|
region.parseOpcode({ "amp_keycenter", "132" });
|
||||||
|
REQUIRE( region.ampKeycenter == 127 );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("amp_keytrack")
|
||||||
|
{
|
||||||
|
REQUIRE( region.ampKeytrack == 0.0f );
|
||||||
|
region.parseOpcode({ "amp_keytrack", "4.2" });
|
||||||
|
REQUIRE( region.ampKeytrack == 4.2f );
|
||||||
|
region.parseOpcode({ "amp_keytrack", "-4.2" });
|
||||||
|
REQUIRE( region.ampKeytrack == -4.2f );
|
||||||
|
region.parseOpcode({ "amp_keytrack", "-123" });
|
||||||
|
REQUIRE( region.ampKeytrack == -96.0f );
|
||||||
|
region.parseOpcode({ "amp_keytrack", "132" });
|
||||||
|
REQUIRE( region.ampKeytrack == 12.0f );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("amp_veltrack")
|
||||||
|
{
|
||||||
|
REQUIRE( region.ampVeltrack == 100.0f );
|
||||||
|
region.parseOpcode({ "amp_veltrack", "4.2" });
|
||||||
|
REQUIRE( region.ampVeltrack == 4.2f );
|
||||||
|
region.parseOpcode({ "amp_veltrack", "-4.2" });
|
||||||
|
REQUIRE( region.ampVeltrack == -4.2f );
|
||||||
|
region.parseOpcode({ "amp_veltrack", "-123" });
|
||||||
|
REQUIRE( region.ampVeltrack == -100.0f );
|
||||||
|
region.parseOpcode({ "amp_veltrack", "132" });
|
||||||
|
REQUIRE( region.ampVeltrack == 100.0f );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("amp_random")
|
||||||
|
{
|
||||||
|
REQUIRE( region.ampRandom == 0.0f );
|
||||||
|
region.parseOpcode({ "amp_random", "4.2" });
|
||||||
|
REQUIRE( region.ampRandom == 4.2f );
|
||||||
|
region.parseOpcode({ "amp_random", "-4.2" });
|
||||||
|
REQUIRE( region.ampRandom == 0.0f );
|
||||||
|
region.parseOpcode({ "amp_random", "132" });
|
||||||
|
REQUIRE( region.ampRandom == 24.0f );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("amp_velcurve")
|
||||||
|
{
|
||||||
|
region.parseOpcode({ "amp_velcurve_6", "0.4" });
|
||||||
|
REQUIRE( region.velocityPoints.back() == std::make_pair<int, float>(6, 0.4f) );
|
||||||
|
region.parseOpcode({ "amp_velcurve_127", "-1.0" });
|
||||||
|
REQUIRE( region.velocityPoints.back() == std::make_pair<int, float>(127, 0.0f) );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("xfin_lokey, xfin_hikey")
|
||||||
|
{
|
||||||
|
REQUIRE(region.crossfadeKeyInRange == Range<uint8_t>(0, 0));
|
||||||
|
region.parseOpcode({"xfin_lokey", "4"});
|
||||||
|
REQUIRE(region.crossfadeKeyInRange == Range<uint8_t>(4, 4));
|
||||||
|
region.parseOpcode({"xfin_lokey", "128"});
|
||||||
|
REQUIRE(region.crossfadeKeyInRange == Range<uint8_t>(127, 127));
|
||||||
|
region.parseOpcode({"xfin_lokey", "59"});
|
||||||
|
REQUIRE(region.crossfadeKeyInRange == Range<uint8_t>(59, 127));
|
||||||
|
region.parseOpcode({"xfin_hikey", "59"});
|
||||||
|
REQUIRE(region.crossfadeKeyInRange == Range<uint8_t>(59, 59));
|
||||||
|
region.parseOpcode({"xfin_hikey", "128"});
|
||||||
|
REQUIRE(region.crossfadeKeyInRange == Range<uint8_t>(59, 127));
|
||||||
|
region.parseOpcode({"xfin_hikey", "0"});
|
||||||
|
REQUIRE(region.crossfadeKeyInRange == Range<uint8_t>(0, 0));
|
||||||
|
region.parseOpcode({"xfin_hikey", "-1"});
|
||||||
|
REQUIRE(region.crossfadeKeyInRange == Range<uint8_t>(0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("xfin_lovel, xfin_hivel")
|
||||||
|
{
|
||||||
|
REQUIRE(region.crossfadeVelInRange == Range<uint8_t>(0, 0));
|
||||||
|
region.parseOpcode({"xfin_lovel", "4"});
|
||||||
|
REQUIRE(region.crossfadeVelInRange == Range<uint8_t>(4, 4));
|
||||||
|
region.parseOpcode({"xfin_lovel", "128"});
|
||||||
|
REQUIRE(region.crossfadeVelInRange == Range<uint8_t>(127, 127));
|
||||||
|
region.parseOpcode({"xfin_lovel", "59"});
|
||||||
|
REQUIRE(region.crossfadeVelInRange == Range<uint8_t>(59, 127));
|
||||||
|
region.parseOpcode({"xfin_hivel", "59"});
|
||||||
|
REQUIRE(region.crossfadeVelInRange == Range<uint8_t>(59, 59));
|
||||||
|
region.parseOpcode({"xfin_hivel", "128"});
|
||||||
|
REQUIRE(region.crossfadeVelInRange == Range<uint8_t>(59, 127));
|
||||||
|
region.parseOpcode({"xfin_hivel", "0"});
|
||||||
|
REQUIRE(region.crossfadeVelInRange == Range<uint8_t>(0, 0));
|
||||||
|
region.parseOpcode({"xfin_hivel", "-1"});
|
||||||
|
REQUIRE(region.crossfadeVelInRange == Range<uint8_t>(0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("xfout_lokey, xfout_hikey")
|
||||||
|
{
|
||||||
|
REQUIRE(region.crossfadeKeyOutRange == Range<uint8_t>(127, 127));
|
||||||
|
region.parseOpcode({"xfout_lokey", "4"});
|
||||||
|
REQUIRE(region.crossfadeKeyOutRange == Range<uint8_t>(4, 127));
|
||||||
|
region.parseOpcode({"xfout_lokey", "128"});
|
||||||
|
REQUIRE(region.crossfadeKeyOutRange == Range<uint8_t>(127, 127));
|
||||||
|
region.parseOpcode({"xfout_lokey", "59"});
|
||||||
|
REQUIRE(region.crossfadeKeyOutRange == Range<uint8_t>(59, 127));
|
||||||
|
region.parseOpcode({"xfout_hikey", "59"});
|
||||||
|
REQUIRE(region.crossfadeKeyOutRange == Range<uint8_t>(59, 59));
|
||||||
|
region.parseOpcode({"xfout_hikey", "128"});
|
||||||
|
REQUIRE(region.crossfadeKeyOutRange == Range<uint8_t>(59, 127));
|
||||||
|
region.parseOpcode({"xfout_hikey", "0"});
|
||||||
|
REQUIRE(region.crossfadeKeyOutRange == Range<uint8_t>(0, 0));
|
||||||
|
region.parseOpcode({"xfout_hikey", "-1"});
|
||||||
|
REQUIRE(region.crossfadeKeyOutRange == Range<uint8_t>(0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("xfout_lovel, xfout_hivel")
|
||||||
|
{
|
||||||
|
REQUIRE(region.crossfadeVelOutRange == Range<uint8_t>(127, 127));
|
||||||
|
region.parseOpcode({"xfout_lovel", "4"});
|
||||||
|
REQUIRE(region.crossfadeVelOutRange == Range<uint8_t>(4, 127));
|
||||||
|
region.parseOpcode({"xfout_lovel", "128"});
|
||||||
|
REQUIRE(region.crossfadeVelOutRange == Range<uint8_t>(127, 127));
|
||||||
|
region.parseOpcode({"xfout_lovel", "59"});
|
||||||
|
REQUIRE(region.crossfadeVelOutRange == Range<uint8_t>(59, 127));
|
||||||
|
region.parseOpcode({"xfout_hivel", "59"});
|
||||||
|
REQUIRE(region.crossfadeVelOutRange == Range<uint8_t>(59, 59));
|
||||||
|
region.parseOpcode({"xfout_hivel", "128"});
|
||||||
|
REQUIRE(region.crossfadeVelOutRange == Range<uint8_t>(59, 127));
|
||||||
|
region.parseOpcode({"xfout_hivel", "0"});
|
||||||
|
REQUIRE(region.crossfadeVelOutRange == Range<uint8_t>(0, 0));
|
||||||
|
region.parseOpcode({"xfout_hivel", "-1"});
|
||||||
|
REQUIRE(region.crossfadeVelOutRange == Range<uint8_t>(0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("xf_keycurve")
|
||||||
|
{
|
||||||
|
REQUIRE(region.crossfadeKeyCurve == SfzCrossfadeCurve::power);
|
||||||
|
region.parseOpcode({"xf_keycurve", "gain"});
|
||||||
|
REQUIRE(region.crossfadeKeyCurve == SfzCrossfadeCurve::gain);
|
||||||
|
region.parseOpcode({"xf_keycurve", "power"});
|
||||||
|
REQUIRE(region.crossfadeKeyCurve == SfzCrossfadeCurve::power);
|
||||||
|
region.parseOpcode({"xf_keycurve", "something"});
|
||||||
|
REQUIRE(region.crossfadeKeyCurve == SfzCrossfadeCurve::power);
|
||||||
|
region.parseOpcode({"xf_keycurve", "gain"});
|
||||||
|
region.parseOpcode({"xf_keycurve", "something"});
|
||||||
|
REQUIRE(region.crossfadeKeyCurve == SfzCrossfadeCurve::gain);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("xf_velcurve")
|
||||||
|
{
|
||||||
|
REQUIRE(region.crossfadeVelCurve == SfzCrossfadeCurve::power);
|
||||||
|
region.parseOpcode({"xf_velcurve", "gain"});
|
||||||
|
REQUIRE(region.crossfadeVelCurve == SfzCrossfadeCurve::gain);
|
||||||
|
region.parseOpcode({"xf_velcurve", "power"});
|
||||||
|
REQUIRE(region.crossfadeVelCurve == SfzCrossfadeCurve::power);
|
||||||
|
region.parseOpcode({"xf_velcurve", "something"});
|
||||||
|
REQUIRE(region.crossfadeVelCurve == SfzCrossfadeCurve::power);
|
||||||
|
region.parseOpcode({"xf_velcurve", "gain"});
|
||||||
|
region.parseOpcode({"xf_velcurve", "something"});
|
||||||
|
REQUIRE(region.crossfadeVelCurve == SfzCrossfadeCurve::gain);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("pitch_keycenter")
|
||||||
|
{
|
||||||
|
REQUIRE( region.pitchKeycenter == 60 );
|
||||||
|
region.parseOpcode({ "pitch_keycenter", "40" });
|
||||||
|
REQUIRE( region.pitchKeycenter == 40 );
|
||||||
|
region.parseOpcode({ "pitch_keycenter", "-1" });
|
||||||
|
REQUIRE( region.pitchKeycenter == 0 );
|
||||||
|
region.parseOpcode({ "pitch_keycenter", "132" });
|
||||||
|
REQUIRE( region.pitchKeycenter == 127 );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("pitch_keytrack")
|
||||||
|
{
|
||||||
|
REQUIRE( region.pitchKeytrack == 100 );
|
||||||
|
region.parseOpcode({ "pitch_keytrack", "40" });
|
||||||
|
REQUIRE( region.pitchKeytrack == 40 );
|
||||||
|
region.parseOpcode({ "pitch_keytrack", "-1" });
|
||||||
|
REQUIRE( region.pitchKeytrack == -1 );
|
||||||
|
region.parseOpcode({ "pitch_keytrack", "1320" });
|
||||||
|
REQUIRE( region.pitchKeytrack == 1200 );
|
||||||
|
region.parseOpcode({ "pitch_keytrack", "-1320" });
|
||||||
|
REQUIRE( region.pitchKeytrack == -1200 );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("pitch_random")
|
||||||
|
{
|
||||||
|
REQUIRE( region.pitchRandom == 0 );
|
||||||
|
region.parseOpcode({ "pitch_random", "40" });
|
||||||
|
REQUIRE( region.pitchRandom == 40 );
|
||||||
|
region.parseOpcode({ "pitch_random", "-1" });
|
||||||
|
REQUIRE( region.pitchRandom == 0 );
|
||||||
|
region.parseOpcode({ "pitch_random", "10320" });
|
||||||
|
REQUIRE( region.pitchRandom == 9600 );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("pitch_veltrack")
|
||||||
|
{
|
||||||
|
REQUIRE( region.pitchVeltrack == 0 );
|
||||||
|
region.parseOpcode({ "pitch_veltrack", "40" });
|
||||||
|
REQUIRE( region.pitchVeltrack == 40 );
|
||||||
|
region.parseOpcode({ "pitch_veltrack", "-1" });
|
||||||
|
REQUIRE( region.pitchVeltrack == -1 );
|
||||||
|
region.parseOpcode({ "pitch_veltrack", "13020" });
|
||||||
|
REQUIRE( region.pitchVeltrack == 9600 );
|
||||||
|
region.parseOpcode({ "pitch_veltrack", "-13020" });
|
||||||
|
REQUIRE( region.pitchVeltrack == -9600 );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("transpose")
|
||||||
|
{
|
||||||
|
REQUIRE( region.transpose == 0 );
|
||||||
|
region.parseOpcode({ "transpose", "40" });
|
||||||
|
REQUIRE( region.transpose == 40 );
|
||||||
|
region.parseOpcode({ "transpose", "-1" });
|
||||||
|
REQUIRE( region.transpose == -1 );
|
||||||
|
region.parseOpcode({ "transpose", "154" });
|
||||||
|
REQUIRE( region.transpose == 127 );
|
||||||
|
region.parseOpcode({ "transpose", "-154" });
|
||||||
|
REQUIRE( region.transpose == -127 );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("tune")
|
||||||
|
{
|
||||||
|
REQUIRE( region.tune == 0 );
|
||||||
|
region.parseOpcode({ "tune", "40" });
|
||||||
|
REQUIRE( region.tune == 40 );
|
||||||
|
region.parseOpcode({ "tune", "-1" });
|
||||||
|
REQUIRE( region.tune == -1 );
|
||||||
|
region.parseOpcode({ "tune", "154" });
|
||||||
|
REQUIRE( region.tune == 100 );
|
||||||
|
region.parseOpcode({ "tune", "-154" });
|
||||||
|
REQUIRE( region.tune == -100 );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ampeg")
|
||||||
|
{
|
||||||
|
// Defaults
|
||||||
|
REQUIRE( region.amplitudeEG.attack == 0.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.decay == 0.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.delay == 0.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.hold == 0.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.release == 0.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.start == 0.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.sustain == 100.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.depth == 0 );
|
||||||
|
REQUIRE( region.amplitudeEG.vel2attack == 0.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.vel2decay == 0.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.vel2delay == 0.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.vel2hold == 0.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.vel2release == 0.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.vel2sustain == 0.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.vel2depth == 0 );
|
||||||
|
//
|
||||||
|
region.parseOpcode({ "ampeg_attack", "1" });
|
||||||
|
region.parseOpcode({ "ampeg_decay", "2" });
|
||||||
|
region.parseOpcode({ "ampeg_delay", "3" });
|
||||||
|
region.parseOpcode({ "ampeg_hold", "4" });
|
||||||
|
region.parseOpcode({ "ampeg_release", "5" });
|
||||||
|
region.parseOpcode({ "ampeg_start", "6" });
|
||||||
|
region.parseOpcode({ "ampeg_sustain", "7" });
|
||||||
|
region.parseOpcode({ "ampeg_depth", "8" });
|
||||||
|
region.parseOpcode({ "ampeg_vel2attack", "9" });
|
||||||
|
region.parseOpcode({ "ampeg_vel2decay", "10" });
|
||||||
|
region.parseOpcode({ "ampeg_vel2delay", "11" });
|
||||||
|
region.parseOpcode({ "ampeg_vel2hold", "12" });
|
||||||
|
region.parseOpcode({ "ampeg_vel2release", "13" });
|
||||||
|
region.parseOpcode({ "ampeg_vel2sustain", "14" });
|
||||||
|
region.parseOpcode({ "ampeg_vel2depth", "15" });
|
||||||
|
REQUIRE( region.amplitudeEG.attack == 1.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.decay == 2.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.delay == 3.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.hold == 4.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.release == 5.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.start == 6.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.sustain == 7.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.depth == 0 ); // ignored for ampeg
|
||||||
|
REQUIRE( region.amplitudeEG.vel2attack == 9.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.vel2decay == 10.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.vel2delay == 11.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.vel2hold == 12.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.vel2release == 13.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.vel2sustain == 14.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.vel2depth == 0 ); // ignored for ampeg
|
||||||
|
//
|
||||||
|
region.parseOpcode({ "ampeg_attack", "1000" });
|
||||||
|
region.parseOpcode({ "ampeg_decay", "1000" });
|
||||||
|
region.parseOpcode({ "ampeg_delay", "1000" });
|
||||||
|
region.parseOpcode({ "ampeg_hold", "1000" });
|
||||||
|
region.parseOpcode({ "ampeg_release", "1000" });
|
||||||
|
region.parseOpcode({ "ampeg_start", "1000" });
|
||||||
|
region.parseOpcode({ "ampeg_sustain", "1000" });
|
||||||
|
region.parseOpcode({ "ampeg_depth", "1000" });
|
||||||
|
region.parseOpcode({ "ampeg_vel2attack", "1000" });
|
||||||
|
region.parseOpcode({ "ampeg_vel2decay", "1000" });
|
||||||
|
region.parseOpcode({ "ampeg_vel2delay", "1000" });
|
||||||
|
region.parseOpcode({ "ampeg_vel2hold", "1000" });
|
||||||
|
region.parseOpcode({ "ampeg_vel2release", "1000" });
|
||||||
|
region.parseOpcode({ "ampeg_vel2sustain", "1000" });
|
||||||
|
region.parseOpcode({ "ampeg_vel2depth", "1000" });
|
||||||
|
REQUIRE( region.amplitudeEG.attack == 100.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.decay == 100.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.delay == 100.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.hold == 100.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.release == 100.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.start == 100.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.sustain == 100.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.depth == 0 ); // ignored for ampeg
|
||||||
|
REQUIRE( region.amplitudeEG.vel2attack == 100.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.vel2decay == 100.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.vel2delay == 100.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.vel2hold == 100.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.vel2release == 100.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.vel2sustain == 100.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.vel2depth == 0 ); // ignored for ampeg
|
||||||
|
//
|
||||||
|
region.parseOpcode({ "ampeg_attack", "-101" });
|
||||||
|
region.parseOpcode({ "ampeg_decay", "-101" });
|
||||||
|
region.parseOpcode({ "ampeg_delay", "-101" });
|
||||||
|
region.parseOpcode({ "ampeg_hold", "-101" });
|
||||||
|
region.parseOpcode({ "ampeg_release", "-101" });
|
||||||
|
region.parseOpcode({ "ampeg_start", "-101" });
|
||||||
|
region.parseOpcode({ "ampeg_sustain", "-101" });
|
||||||
|
region.parseOpcode({ "ampeg_depth", "-101" });
|
||||||
|
region.parseOpcode({ "ampeg_vel2attack", "-101" });
|
||||||
|
region.parseOpcode({ "ampeg_vel2decay", "-101" });
|
||||||
|
region.parseOpcode({ "ampeg_vel2delay", "-101" });
|
||||||
|
region.parseOpcode({ "ampeg_vel2hold", "-101" });
|
||||||
|
region.parseOpcode({ "ampeg_vel2release", "-101" });
|
||||||
|
region.parseOpcode({ "ampeg_vel2sustain", "-101" });
|
||||||
|
region.parseOpcode({ "ampeg_vel2depth", "-101" });
|
||||||
|
REQUIRE( region.amplitudeEG.attack == 0.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.decay == 0.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.delay == 0.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.hold == 0.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.release == 0.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.start == 0.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.sustain == 0.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.depth == 0 ); // ignored for ampeg
|
||||||
|
REQUIRE( region.amplitudeEG.vel2attack == -100.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.vel2decay == -100.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.vel2delay == -100.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.vel2hold == -100.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.vel2release == -100.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.vel2sustain == -100.0f );
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ampeg_XX_onccNN")
|
||||||
|
{
|
||||||
|
// Defaults
|
||||||
|
REQUIRE( !region.amplitudeEG.ccAttack );
|
||||||
|
REQUIRE( !region.amplitudeEG.ccDecay );
|
||||||
|
REQUIRE( !region.amplitudeEG.ccDelay );
|
||||||
|
REQUIRE( !region.amplitudeEG.ccHold );
|
||||||
|
REQUIRE( !region.amplitudeEG.ccRelease );
|
||||||
|
REQUIRE( !region.amplitudeEG.ccStart );
|
||||||
|
REQUIRE( !region.amplitudeEG.ccSustain );
|
||||||
|
//
|
||||||
|
region.parseOpcode({ "ampeg_attack_oncc1", "1" });
|
||||||
|
region.parseOpcode({ "ampeg_decay_oncc2", "2" });
|
||||||
|
region.parseOpcode({ "ampeg_delay_oncc3", "3" });
|
||||||
|
region.parseOpcode({ "ampeg_hold_oncc4", "4" });
|
||||||
|
region.parseOpcode({ "ampeg_release_oncc5", "5" });
|
||||||
|
region.parseOpcode({ "ampeg_start_oncc6", "6" });
|
||||||
|
region.parseOpcode({ "ampeg_sustain_oncc7", "7" });
|
||||||
|
REQUIRE( region.amplitudeEG.ccAttack );
|
||||||
|
REQUIRE( region.amplitudeEG.ccDecay );
|
||||||
|
REQUIRE( region.amplitudeEG.ccDelay );
|
||||||
|
REQUIRE( region.amplitudeEG.ccHold );
|
||||||
|
REQUIRE( region.amplitudeEG.ccRelease );
|
||||||
|
REQUIRE( region.amplitudeEG.ccStart );
|
||||||
|
REQUIRE( region.amplitudeEG.ccSustain );
|
||||||
|
REQUIRE( region.amplitudeEG.ccAttack->first == 1 );
|
||||||
|
REQUIRE( region.amplitudeEG.ccDecay->first == 2 );
|
||||||
|
REQUIRE( region.amplitudeEG.ccDelay->first == 3 );
|
||||||
|
REQUIRE( region.amplitudeEG.ccHold->first == 4 );
|
||||||
|
REQUIRE( region.amplitudeEG.ccRelease->first == 5 );
|
||||||
|
REQUIRE( region.amplitudeEG.ccStart->first == 6 );
|
||||||
|
REQUIRE( region.amplitudeEG.ccSustain->first == 7 );
|
||||||
|
REQUIRE( region.amplitudeEG.ccAttack->second == 1.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.ccDecay->second == 2.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.ccDelay->second == 3.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.ccHold->second == 4.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.ccRelease->second == 5.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.ccStart->second == 6.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.ccSustain->second == 7.0f );
|
||||||
|
//
|
||||||
|
region.parseOpcode({ "ampeg_attack_oncc1", "101" });
|
||||||
|
region.parseOpcode({ "ampeg_decay_oncc2", "101" });
|
||||||
|
region.parseOpcode({ "ampeg_delay_oncc3", "101" });
|
||||||
|
region.parseOpcode({ "ampeg_hold_oncc4", "101" });
|
||||||
|
region.parseOpcode({ "ampeg_release_oncc5", "101" });
|
||||||
|
region.parseOpcode({ "ampeg_start_oncc6", "101" });
|
||||||
|
region.parseOpcode({ "ampeg_sustain_oncc7", "101" });
|
||||||
|
REQUIRE( region.amplitudeEG.ccAttack->second == 100.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.ccDecay->second == 100.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.ccDelay->second == 100.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.ccHold->second == 100.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.ccRelease->second == 100.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.ccStart->second == 100.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.ccSustain->second == 100.0f );
|
||||||
|
//
|
||||||
|
region.parseOpcode({ "ampeg_attack_oncc1", "-101" });
|
||||||
|
region.parseOpcode({ "ampeg_decay_oncc2", "-101" });
|
||||||
|
region.parseOpcode({ "ampeg_delay_oncc3", "-101" });
|
||||||
|
region.parseOpcode({ "ampeg_hold_oncc4", "-101" });
|
||||||
|
region.parseOpcode({ "ampeg_release_oncc5", "-101" });
|
||||||
|
region.parseOpcode({ "ampeg_start_oncc6", "-101" });
|
||||||
|
region.parseOpcode({ "ampeg_sustain_oncc7", "-101" });
|
||||||
|
REQUIRE( region.amplitudeEG.ccAttack->second == -100.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.ccDecay->second == -100.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.ccDelay->second == -100.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.ccHold->second == -100.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.ccRelease->second == -100.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.ccStart->second == -100.0f );
|
||||||
|
REQUIRE( region.amplitudeEG.ccSustain->second == -100.0f );
|
||||||
|
}
|
||||||
|
}
|
||||||
1
tests/TestFiles/Includes/included.sfz
Normal file
1
tests/TestFiles/Includes/included.sfz
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<region> sample=dummy.wav
|
||||||
1
tests/TestFiles/Includes/included_2.sfz
Normal file
1
tests/TestFiles/Includes/included_2.sfz
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<region> sample=dummy2.wav
|
||||||
2
tests/TestFiles/Includes/included_loop1.sfz
Normal file
2
tests/TestFiles/Includes/included_loop1.sfz
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
#include "included_loop2.sfz"
|
||||||
|
<region> sample=dummy_loop1.wav
|
||||||
3
tests/TestFiles/Includes/included_loop2.sfz
Normal file
3
tests/TestFiles/Includes/included_loop2.sfz
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
#include "included_loop1.sfz"
|
||||||
|
#include "root_loop.sfz"
|
||||||
|
<region> sample=dummy_loop2.wav
|
||||||
2
tests/TestFiles/Includes/included_recursive1.sfz
Normal file
2
tests/TestFiles/Includes/included_recursive1.sfz
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
#include "included_recursive2.sfz"
|
||||||
|
<region> sample=dummy_recursive1.wav
|
||||||
1
tests/TestFiles/Includes/included_recursive2.sfz
Normal file
1
tests/TestFiles/Includes/included_recursive2.sfz
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<region> sample=dummy_recursive2.wav
|
||||||
2
tests/TestFiles/Includes/multiple_includes.sfz
Normal file
2
tests/TestFiles/Includes/multiple_includes.sfz
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
#include "included.sfz"
|
||||||
|
#include "included_2.sfz"
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
//DUMMY comment
|
||||||
|
|
||||||
|
#include "included.sfz"
|
||||||
|
#include "included_2.sfz"
|
||||||
|
|
||||||
|
//EOF
|
||||||
1
tests/TestFiles/Includes/root_local.sfz
Normal file
1
tests/TestFiles/Includes/root_local.sfz
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
#include "included.sfz"
|
||||||
1
tests/TestFiles/Includes/root_loop.sfz
Normal file
1
tests/TestFiles/Includes/root_loop.sfz
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
#include "included_loop1.sfz"
|
||||||
1
tests/TestFiles/Includes/root_recursive.sfz
Normal file
1
tests/TestFiles/Includes/root_recursive.sfz
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
#include "included_recursive1.sfz"
|
||||||
1
tests/TestFiles/Includes/root_subdir.sfz
Normal file
1
tests/TestFiles/Includes/root_subdir.sfz
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
#include "subdir/included_subdir.sfz"
|
||||||
1
tests/TestFiles/Includes/root_subdir_win.sfz
Normal file
1
tests/TestFiles/Includes/root_subdir_win.sfz
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
#include "subdir\included_subdir.sfz"
|
||||||
1
tests/TestFiles/Includes/subdir/included_subdir.sfz
Normal file
1
tests/TestFiles/Includes/subdir/included_subdir.sfz
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<region> sample=dummy_subdir.wav
|
||||||
0
tests/TestFiles/Regions/dummy.1.wav
Normal file
0
tests/TestFiles/Regions/dummy.1.wav
Normal file
0
tests/TestFiles/Regions/dummy.2.wav
Normal file
0
tests/TestFiles/Regions/dummy.2.wav
Normal file
0
tests/TestFiles/Regions/dummy.wav
Normal file
0
tests/TestFiles/Regions/dummy.wav
Normal file
3
tests/TestFiles/Regions/regions_many.sfz
Normal file
3
tests/TestFiles/Regions/regions_many.sfz
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
<region> sample=dummy.wav
|
||||||
|
<region> sample=dummy.1.wav
|
||||||
|
<region> sample=dummy.2.wav
|
||||||
1
tests/TestFiles/Regions/regions_one.sfz
Normal file
1
tests/TestFiles/Regions/regions_one.sfz
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<region> sample=dummy.wav
|
||||||
1
tests/TestFiles/Regions/regions_opcodes.sfz
Normal file
1
tests/TestFiles/Regions/regions_opcodes.sfz
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<region> lochan=2 hichan=14 sample=dummy.wav
|
||||||
1
tests/TestFiles/Regions/underscore_opcodes.sfz
Normal file
1
tests/TestFiles/Regions/underscore_opcodes.sfz
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<region> loop_mode=loop_sustain sample=dummy.wav
|
||||||
24
tests/TestFiles/SpecificBugs/MeatBassPizz/Programs/pizz.sfz
Normal file
24
tests/TestFiles/SpecificBugs/MeatBassPizz/Programs/pizz.sfz
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
<group>
|
||||||
|
lokey=12
|
||||||
|
hikey=22
|
||||||
|
lovel=97
|
||||||
|
pitch_keycenter=21
|
||||||
|
hicc107=13
|
||||||
|
|
||||||
|
<region>
|
||||||
|
sample=..\Samples\pizz\a0_vl4_rr1.wav
|
||||||
|
hirand=0.250
|
||||||
|
|
||||||
|
<region>
|
||||||
|
sample=..\Samples\pizz\a0_vl4_rr2.wav
|
||||||
|
lorand=0.250
|
||||||
|
hirand=0.500
|
||||||
|
|
||||||
|
<region>
|
||||||
|
sample=..\Samples\pizz\a0_vl4_rr3.wav
|
||||||
|
lorand=0.500
|
||||||
|
hirand=0.750
|
||||||
|
|
||||||
|
<region>
|
||||||
|
sample=..\Samples\pizz\a0_vl4_rr4.wav
|
||||||
|
lorand=0.750
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
25
tests/TestFiles/basic_hierarchy.sfz
Normal file
25
tests/TestFiles/basic_hierarchy.sfz
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
<global> width=40
|
||||||
|
|
||||||
|
<master> pan=30
|
||||||
|
|
||||||
|
<group>
|
||||||
|
delay=67
|
||||||
|
<region> key=60 sample=Regions/dummy.wav
|
||||||
|
<region> key=61 sample=Regions/dummy.1.wav
|
||||||
|
|
||||||
|
<group>
|
||||||
|
delay=56
|
||||||
|
<region> key=50 sample=Regions/dummy.wav
|
||||||
|
<region> key=51 sample=Regions/dummy.1.wav
|
||||||
|
|
||||||
|
<master> pan=-10
|
||||||
|
|
||||||
|
<group>
|
||||||
|
delay=47
|
||||||
|
<region> key=40 sample=Regions/dummy.wav
|
||||||
|
<region> key=41 sample=Regions/dummy.1.wav
|
||||||
|
|
||||||
|
<group>
|
||||||
|
delay=36
|
||||||
|
<region> key=30 sample=Regions/dummy.wav
|
||||||
|
<region> key=31 sample=Regions/dummy.1.wav
|
||||||
25
tests/TestFiles/basic_hierarchy_antislash.sfz
Normal file
25
tests/TestFiles/basic_hierarchy_antislash.sfz
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
<global> width=40
|
||||||
|
|
||||||
|
<master> pan=30
|
||||||
|
|
||||||
|
<group>
|
||||||
|
delay=67
|
||||||
|
<region> key=60 sample=Regions\dummy.wav
|
||||||
|
<region> key=61 sample=Regions\dummy.1.wav
|
||||||
|
|
||||||
|
<group>
|
||||||
|
delay=56
|
||||||
|
<region> key=50 sample=Regions\dummy.wav
|
||||||
|
<region> key=51 sample=Regions\dummy.1.wav
|
||||||
|
|
||||||
|
<master> pan=-10
|
||||||
|
|
||||||
|
<group>
|
||||||
|
delay=47
|
||||||
|
<region> key=40 sample=Regions\dummy.wav
|
||||||
|
<region> key=41 sample=Regions\dummy.1.wav
|
||||||
|
|
||||||
|
<group>
|
||||||
|
delay=36
|
||||||
|
<region> key=30 sample=Regions\dummy.wav
|
||||||
|
<region> key=31 sample=Regions\dummy.1.wav
|
||||||
8
tests/TestFiles/defines.sfz
Normal file
8
tests/TestFiles/defines.sfz
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
<control>
|
||||||
|
#define $KICKKEY 36
|
||||||
|
#define $SNAREKEY 38
|
||||||
|
#define $HATKEY 42
|
||||||
|
|
||||||
|
<region>key=$KICKKEY sample=kick.wav
|
||||||
|
<region>key=$SNAREKEY sample=snare.wav
|
||||||
|
<region>key=$HATKEY sample=closedhat.wav
|
||||||
6
tests/TestFiles/groups_avl.sfz
Normal file
6
tests/TestFiles/groups_avl.sfz
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<group> key=36 volume=6
|
||||||
|
<region> lovel=1 hivel=26 sample=36-CajonCenter-1.wav
|
||||||
|
<region> lovel=27 hivel=52 sample=36-CajonCenter-2.wav
|
||||||
|
<region> lovel=53 hivel=77 sample=36-CajonCenter-3.wav
|
||||||
|
<region> lovel=78 hivel=102 sample=36-CajonCenter-4.wav
|
||||||
|
<region> lovel=103 hivel=127 sample=36-CajonCenter-5.wav
|
||||||
5
tests/TestFiles/sw_default.sfz
Normal file
5
tests/TestFiles/sw_default.sfz
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<global> sw_lokey=30 sw_hikey=50 sw_default=40
|
||||||
|
<region> sw_last=41 key=51 sample=*silence
|
||||||
|
<region> sw_last=40 key=52 sample=*silence
|
||||||
|
<region> sw_last=41 key=53 sample=*silence
|
||||||
|
<region> sw_last=40 key=54 sample=*silence
|
||||||
Loading…
Add table
Reference in a new issue