Add the tuning control
This commit is contained in:
parent
4b2d265240
commit
dfd10a0977
20 changed files with 751 additions and 13 deletions
|
|
@ -78,6 +78,9 @@ target_include_directories(sfizz-pugixml PUBLIC "src/external/pugixml/src")
|
|||
add_library(sfizz-spline STATIC "src/external/spline/spline/spline.cpp")
|
||||
target_include_directories(sfizz-spline PUBLIC "src/external/spline")
|
||||
|
||||
add_library(sfizz-tunings STATIC "src/external/tunings/src/Tunings.cpp")
|
||||
target_include_directories(sfizz-tunings PUBLIC "src/external/tunings/include")
|
||||
|
||||
include (CheckLibraryExists)
|
||||
add_library (sfizz-atomic INTERFACE)
|
||||
if (UNIX AND NOT APPLE)
|
||||
|
|
|
|||
1
dpf.mk
1
dpf.mk
|
|
@ -99,6 +99,7 @@ SFIZZ_SOURCES = \
|
|||
src/sfizz/SIMDNEON.cpp \
|
||||
src/sfizz/SIMDSSE.cpp \
|
||||
src/sfizz/Synth.cpp \
|
||||
src/sfizz/Tuning.cpp \
|
||||
src/sfizz/Voice.cpp \
|
||||
src/sfizz/Wavetables.cpp
|
||||
|
||||
|
|
|
|||
70
lv2/sfizz.c
70
lv2/sfizz.c
|
|
@ -61,6 +61,7 @@
|
|||
#define SFIZZ_URI "http://sfztools.github.io/sfizz"
|
||||
#define SFIZZ_PREFIX SFIZZ_URI "#"
|
||||
#define SFIZZ__sfzFile "http://sfztools.github.io/sfizz:sfzfile"
|
||||
#define SFIZZ__scalaFile "http://sfztools.github.io/sfizz:scalafile"
|
||||
#define SFIZZ__numVoices "http://sfztools.github.io/sfizz:numvoices"
|
||||
#define SFIZZ__preloadSize "http://sfztools.github.io/sfizz:preload_size"
|
||||
#define SFIZZ__oversampling "http://sfztools.github.io/sfizz:oversampling"
|
||||
|
|
@ -105,6 +106,8 @@ typedef struct
|
|||
const float *oversampling_port;
|
||||
const float *preload_port;
|
||||
const float *freewheel_port;
|
||||
const float *scala_root_key_port;
|
||||
const float *tuning_frequency_port;
|
||||
|
||||
// Atom forge
|
||||
LV2_Atom_Forge forge; ///< Forge for writing atoms in run thread
|
||||
|
|
@ -132,6 +135,7 @@ typedef struct
|
|||
LV2_URID patch_body_uri;
|
||||
LV2_URID state_changed_uri;
|
||||
LV2_URID sfizz_sfz_file_uri;
|
||||
LV2_URID sfizz_scala_file_uri;
|
||||
LV2_URID sfizz_num_voices_uri;
|
||||
LV2_URID sfizz_preload_size_uri;
|
||||
LV2_URID sfizz_oversampling_uri;
|
||||
|
|
@ -142,6 +146,7 @@ typedef struct
|
|||
sfizz_synth_t *synth;
|
||||
bool expect_nominal_block_length;
|
||||
char sfz_file_path[MAX_PATH_SIZE];
|
||||
char scala_file_path[MAX_PATH_SIZE];
|
||||
int num_voices;
|
||||
unsigned int preload_size;
|
||||
sfizz_oversampling_factor_t oversampling;
|
||||
|
|
@ -162,7 +167,9 @@ enum
|
|||
SFIZZ_POLYPHONY = 5,
|
||||
SFIZZ_OVERSAMPLING = 6,
|
||||
SFIZZ_PRELOAD = 7,
|
||||
SFIZZ_FREEWHEELING = 8
|
||||
SFIZZ_FREEWHEELING = 8,
|
||||
SFIZZ_SCALA_ROOT_KEY = 9,
|
||||
SFIZZ_TUNING_FREQUENCY = 10,
|
||||
};
|
||||
|
||||
static void
|
||||
|
|
@ -186,6 +193,7 @@ sfizz_lv2_map_required_uris(sfizz_plugin_t *self)
|
|||
self->patch_value_uri = map->map(map->handle, LV2_PATCH__value);
|
||||
self->state_changed_uri = map->map(map->handle, LV2_STATE__StateChanged);
|
||||
self->sfizz_sfz_file_uri = map->map(map->handle, SFIZZ__sfzFile);
|
||||
self->sfizz_scala_file_uri = map->map(map->handle, SFIZZ__scalaFile);
|
||||
self->sfizz_num_voices_uri = map->map(map->handle, SFIZZ__numVoices);
|
||||
self->sfizz_preload_size_uri = map->map(map->handle, SFIZZ__preloadSize);
|
||||
self->sfizz_oversampling_uri = map->map(map->handle, SFIZZ__oversampling);
|
||||
|
|
@ -228,6 +236,12 @@ connect_port(LV2_Handle instance,
|
|||
case SFIZZ_FREEWHEELING:
|
||||
self->freewheel_port = (const float *)data;
|
||||
break;
|
||||
case SFIZZ_SCALA_ROOT_KEY:
|
||||
self->scala_root_key_port = (const float *)data;
|
||||
break;
|
||||
case SFIZZ_TUNING_FREQUENCY:
|
||||
self->tuning_frequency_port = (const float *)data;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
@ -284,6 +298,7 @@ instantiate(const LV2_Descriptor *descriptor,
|
|||
self->sample_rate = (float)rate;
|
||||
self->expect_nominal_block_length = false;
|
||||
self->sfz_file_path[0] = '\0';
|
||||
self->scala_file_path[0] = '\0';
|
||||
self->num_voices = DEFAULT_VOICES;
|
||||
self->oversampling = DEFAULT_OVERSAMPLING;
|
||||
self->preload_size = DEFAULT_PRELOAD;
|
||||
|
|
@ -420,15 +435,15 @@ deactivate(LV2_Handle instance)
|
|||
}
|
||||
|
||||
static void
|
||||
sfizz_lv2_send_file_path(sfizz_plugin_t *self)
|
||||
sfizz_lv2_send_file_path(sfizz_plugin_t *self, LV2_URID urid, const char *path)
|
||||
{
|
||||
LV2_Atom_Forge_Frame frame;
|
||||
lv2_atom_forge_frame_time(&self->forge, 0);
|
||||
lv2_atom_forge_object(&self->forge, &frame, 0, self->patch_set_uri);
|
||||
lv2_atom_forge_key(&self->forge, self->patch_property_uri);
|
||||
lv2_atom_forge_urid(&self->forge, self->sfizz_sfz_file_uri);
|
||||
lv2_atom_forge_urid(&self->forge, urid);
|
||||
lv2_atom_forge_key(&self->forge, self->patch_value_uri);
|
||||
lv2_atom_forge_path(&self->forge, self->sfz_file_path, (uint32_t)strlen(self->sfz_file_path));
|
||||
lv2_atom_forge_path(&self->forge, path, (uint32_t)strlen(path));
|
||||
lv2_atom_forge_pop(&self->forge, &frame);
|
||||
}
|
||||
|
||||
|
|
@ -477,6 +492,18 @@ sfizz_lv2_handle_atom_object(sfizz_plugin_t *self, const LV2_Atom_Object *obj)
|
|||
self->worker->schedule_work(self->worker->handle, null_terminated_atom_size, sfz_file_path);
|
||||
self->check_modification = false;
|
||||
}
|
||||
else if (key == self->sfizz_scala_file_uri)
|
||||
{
|
||||
const uint32_t original_atom_size = lv2_atom_total_size((const LV2_Atom *)atom);
|
||||
const uint32_t null_terminated_atom_size = original_atom_size + 1;
|
||||
char atom_buffer[MAX_PATH_SIZE];
|
||||
memcpy(&atom_buffer, atom, original_atom_size);
|
||||
atom_buffer[original_atom_size] = 0; // Null terminate the string for safety
|
||||
LV2_Atom *scala_file_path = (LV2_Atom *)&atom_buffer;
|
||||
scala_file_path->type = self->sfizz_scala_file_uri;
|
||||
self->worker->schedule_work(self->worker->handle, null_terminated_atom_size, scala_file_path);
|
||||
self->check_modification = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
lv2_log_warning(&self->logger, "[sfizz] Unknown or unsupported object\n");
|
||||
|
|
@ -657,11 +684,16 @@ run(LV2_Handle instance, uint32_t sample_count)
|
|||
lv2_atom_object_get(obj, self->patch_property_uri, &property, 0);
|
||||
if (!property) // Send the full state
|
||||
{
|
||||
sfizz_lv2_send_file_path(self);
|
||||
sfizz_lv2_send_file_path(self, self->sfizz_sfz_file_uri, self->sfz_file_path);
|
||||
sfizz_lv2_send_file_path(self, self->sfizz_scala_file_uri, self->scala_file_path);
|
||||
}
|
||||
else if (property->body == self->sfizz_sfz_file_uri)
|
||||
{
|
||||
sfizz_lv2_send_file_path(self);
|
||||
sfizz_lv2_send_file_path(self, self->sfizz_sfz_file_uri, self->sfz_file_path);
|
||||
}
|
||||
else if (property->body == self->sfizz_scala_file_uri)
|
||||
{
|
||||
sfizz_lv2_send_file_path(self, self->sfizz_scala_file_uri, self->scala_file_path);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
@ -685,6 +717,8 @@ run(LV2_Handle instance, uint32_t sample_count)
|
|||
// Check and update parameters if needed
|
||||
sfizz_lv2_check_freewheeling(self);
|
||||
sfizz_set_volume(self->synth, *(self->volume_port));
|
||||
sfizz_set_scala_root_key(self->synth, *(self->scala_root_key_port));
|
||||
sfizz_set_tuning_frequency(self->synth, *(self->tuning_frequency_port));
|
||||
sfizz_lv2_check_preload_size(self);
|
||||
sfizz_lv2_check_oversampling(self);
|
||||
sfizz_lv2_check_num_voices(self);
|
||||
|
|
@ -838,6 +872,13 @@ restore(LV2_Handle instance,
|
|||
}
|
||||
}
|
||||
|
||||
value = retrieve(handle, self->sfizz_scala_file_uri, &size, &type, &val_flags);
|
||||
if (value)
|
||||
{
|
||||
lv2_log_note(&self->logger, "[sfizz] Restoring the scale %s\n", (const char *)value);
|
||||
sfizz_load_scala_file(self->synth, (const char *)value);
|
||||
}
|
||||
|
||||
value = retrieve(handle, self->sfizz_num_voices_uri, &size, &type, &val_flags);
|
||||
if (value)
|
||||
{
|
||||
|
|
@ -894,6 +935,14 @@ save(LV2_Handle instance,
|
|||
self->atom_path_uri,
|
||||
LV2_STATE_IS_POD);
|
||||
|
||||
// Save the scala file path
|
||||
store(handle,
|
||||
self->sfizz_scala_file_uri,
|
||||
self->scala_file_path,
|
||||
strlen(self->scala_file_path) + 1,
|
||||
self->atom_path_uri,
|
||||
LV2_STATE_IS_POD);
|
||||
|
||||
// Save the number of voices
|
||||
store(handle,
|
||||
self->sfizz_num_voices_uri,
|
||||
|
|
@ -952,6 +1001,15 @@ work(LV2_Handle instance,
|
|||
};
|
||||
respond(handle, lv2_atom_total_size(&check_modification_atom), &check_modification_atom);
|
||||
}
|
||||
else if (atom->type == self->sfizz_scala_file_uri)
|
||||
{
|
||||
const char *scala_file_path = LV2_ATOM_BODY_CONST(atom);
|
||||
if (sfizz_load_scala_file(self->synth, scala_file_path)) {
|
||||
lv2_log_note(&self->logger, "[sfizz] Scala file loaded: %s\n", scala_file_path);
|
||||
} else {
|
||||
lv2_log_error(&self->logger, "[sfizz] Error with %s; no scala file should be loaded\n", scala_file_path);
|
||||
}
|
||||
}
|
||||
else if (atom->type == self->sfizz_num_voices_uri)
|
||||
{
|
||||
const int num_voices = *(const int *)LV2_ATOM_BODY_CONST(atom);
|
||||
|
|
|
|||
|
|
@ -27,6 +27,12 @@ midnam:update a lv2:Feature .
|
|||
"Configuration"@fr ,
|
||||
"Impostazioni"@it .
|
||||
|
||||
<@LV2PLUGIN_URI@#tuning>
|
||||
a pg:Group ;
|
||||
lv2:symbol "tuning" ;
|
||||
lv2:name "Tuning",
|
||||
"Accordage"@fr .
|
||||
|
||||
<@LV2PLUGIN_URI@:sfzfile>
|
||||
a lv2:Parameter ;
|
||||
rdfs:label "SFZ file",
|
||||
|
|
@ -34,6 +40,14 @@ midnam:update a lv2:Feature .
|
|||
"File SFZ"@it ;
|
||||
rdfs:range atom:Path .
|
||||
|
||||
<@LV2PLUGIN_URI@:scalafile>
|
||||
a lv2:Parameter ;
|
||||
pg:group <@LV2PLUGIN_URI@#tuning> ;
|
||||
rdfs:label "Scala file",
|
||||
"Fichier Scala"@fr ,
|
||||
"File Scala"@it ;
|
||||
rdfs:range atom:Path .
|
||||
|
||||
<@LV2PLUGIN_URI@>
|
||||
a doap:Project, lv2:Plugin, lv2:InstrumentPlugin ;
|
||||
|
||||
|
|
@ -62,6 +76,7 @@ midnam:update a lv2:Feature .
|
|||
opts:supportedOption bufsize:maxBlockLength, bufsize:nominalBlockLength ;
|
||||
|
||||
patch:writable <@LV2PLUGIN_URI@:sfzfile> ;
|
||||
patch:writable <@LV2PLUGIN_URI@:scalafile> ;
|
||||
|
||||
lv2:port [
|
||||
a lv2:InputPort, atom:AtomPort ;
|
||||
|
|
@ -242,4 +257,27 @@ midnam:update a lv2:Feature .
|
|||
lv2:default 0 ;
|
||||
lv2:minimum 0 ;
|
||||
lv2:maximum 1 ;
|
||||
] , [
|
||||
a lv2:InputPort, lv2:ControlPort ;
|
||||
lv2:index 9 ;
|
||||
lv2:symbol "scala_root_key" ;
|
||||
lv2:name "Scala root key",
|
||||
"Tonalité de base Scala"@fr ;
|
||||
pg:group <@LV2PLUGIN_URI@#tuning> ;
|
||||
lv2:portProperty lv2:integer ;
|
||||
lv2:default 60 ;
|
||||
lv2:minimum 0 ;
|
||||
lv2:maximum 127 ;
|
||||
units:unit units:midiNote
|
||||
] , [
|
||||
a lv2:InputPort, lv2:ControlPort ;
|
||||
lv2:index 10 ;
|
||||
lv2:symbol "tuning_frequency" ;
|
||||
lv2:name "Tuning frequency",
|
||||
"Fréquence d'accordage"@fr ;
|
||||
pg:group <@LV2PLUGIN_URI@#tuning> ;
|
||||
lv2:default 440.0 ;
|
||||
lv2:minimum 300.0 ;
|
||||
lv2:maximum 500.0 ;
|
||||
units:unit units:hz
|
||||
] .
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ set (SFIZZ_SOURCES
|
|||
sfizz/SfzFilter.cpp
|
||||
sfizz/Curve.cpp
|
||||
sfizz/Wavetables.cpp
|
||||
sfizz/Tuning.cpp
|
||||
sfizz/RTSemaphore.cpp
|
||||
sfizz/Effects.cpp
|
||||
sfizz/effects/Nothing.cpp
|
||||
|
|
@ -58,7 +59,7 @@ target_sources(sfizz_static PRIVATE ${SFIZZ_SOURCES} sfizz/sfizz_wrapper.cpp sfi
|
|||
target_include_directories (sfizz_static PUBLIC .)
|
||||
target_include_directories (sfizz_static PUBLIC external)
|
||||
target_link_libraries (sfizz_static PUBLIC absl::strings absl::span)
|
||||
target_link_libraries (sfizz_static PRIVATE sfizz_parser absl::flat_hash_map Threads::Threads sfizz-sndfile sfizz-pugixml sfizz-spline sfizz-kissfft sfizz-cpuid sfizz-atomic)
|
||||
target_link_libraries (sfizz_static PRIVATE sfizz_parser absl::flat_hash_map Threads::Threads sfizz-sndfile sfizz-pugixml sfizz-spline sfizz-tunings sfizz-kissfft sfizz-cpuid sfizz-atomic)
|
||||
set_target_properties (sfizz_static PROPERTIES OUTPUT_NAME sfizz PUBLIC_HEADER "sfizz.h;sfizz.hpp")
|
||||
if (WIN32)
|
||||
target_compile_definitions (sfizz_static PRIVATE _USE_MATH_DEFINES)
|
||||
|
|
@ -95,7 +96,7 @@ if (SFIZZ_SHARED)
|
|||
target_sources(sfizz_shared PRIVATE ${SFIZZ_SOURCES} sfizz/sfizz_wrapper.cpp sfizz/sfizz.cpp)
|
||||
target_include_directories (sfizz_shared PRIVATE .)
|
||||
target_include_directories (sfizz_shared PRIVATE external)
|
||||
target_link_libraries (sfizz_shared PRIVATE absl::strings absl::span sfizz_parser absl::flat_hash_map Threads::Threads sfizz-sndfile sfizz-pugixml sfizz-spline sfizz-kissfft sfizz-cpuid sfizz-atomic)
|
||||
target_link_libraries (sfizz_shared PRIVATE absl::strings absl::span sfizz_parser absl::flat_hash_map Threads::Threads sfizz-sndfile sfizz-pugixml sfizz-spline sfizz-tunings sfizz-kissfft sfizz-cpuid sfizz-atomic)
|
||||
if (WIN32)
|
||||
target_compile_definitions (sfizz_shared PRIVATE _USE_MATH_DEFINES)
|
||||
endif()
|
||||
|
|
|
|||
52
src/sfizz.h
52
src/sfizz.h
|
|
@ -85,6 +85,58 @@ SFIZZ_EXPORTED_API bool sfizz_load_file(sfizz_synth_t* synth, const char* path);
|
|||
*/
|
||||
SFIZZ_EXPORTED_API bool sfizz_load_string(sfizz_synth_t* synth, const char* path, const char* text);
|
||||
|
||||
/**
|
||||
* @brief Sets the tuning from a Scala file loaded from the file system.
|
||||
*
|
||||
* @param synth The sfizz synth.
|
||||
* @param path The path to the file in Scala format.
|
||||
* @return @true when tuning scale loaded OK,
|
||||
* @false if some error occurred.
|
||||
*/
|
||||
SFIZZ_EXPORTED_API bool sfizz_load_scala_file(sfizz_synth_t* synth, const char* path);
|
||||
|
||||
/**
|
||||
* @brief Sets the tuning from a Scala file loaded from memory.
|
||||
*
|
||||
* @param synth The sfizz synth.
|
||||
* @param text The contents of the file in Scala format.
|
||||
* @return @true when tuning scale loaded OK,
|
||||
* @false if some error occurred.
|
||||
*/
|
||||
SFIZZ_EXPORTED_API bool sfizz_load_scala_string(sfizz_synth_t* synth, const char* text);
|
||||
|
||||
/**
|
||||
* @brief Sets the scala root key.
|
||||
*
|
||||
* @param synth The sfizz synth.
|
||||
* @param root_key The MIDI number of the Scala root key (default 60 for C4).
|
||||
*/
|
||||
SFIZZ_EXPORTED_API void sfizz_set_scala_root_key(sfizz_synth_t* synth, int root_key);
|
||||
|
||||
/**
|
||||
* @brief Gets the scala root key.
|
||||
*
|
||||
* @param synth The sfizz synth.
|
||||
* @return The MIDI number of the Scala root key (default 60 for C4).
|
||||
*/
|
||||
SFIZZ_EXPORTED_API int sfizz_get_scala_root_key(sfizz_synth_t* synth);
|
||||
|
||||
/**
|
||||
* @brief Sets the reference tuning frequency.
|
||||
*
|
||||
* @param synth The sfizz synth.
|
||||
* @param frequency The frequency which indicates where standard tuning A4 is (default 440 Hz).
|
||||
*/
|
||||
SFIZZ_EXPORTED_API void sfizz_set_tuning_frequency(sfizz_synth_t* synth, float frequency);
|
||||
|
||||
/**
|
||||
* @brief Gets the reference tuning frequency.
|
||||
*
|
||||
* @param synth The sfizz synth.
|
||||
* @return The frequency which indicates where standard tuning A4 is (default 440 Hz).
|
||||
*/
|
||||
SFIZZ_EXPORTED_API float sfizz_get_tuning_frequency(sfizz_synth_t* synth);
|
||||
|
||||
/**
|
||||
* @brief Return the number of regions in the currently loaded SFZ file.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -73,6 +73,52 @@ public:
|
|||
*/
|
||||
bool loadSfzString(const std::string& path, const std::string& text);
|
||||
|
||||
/**
|
||||
* @brief Sets the tuning from a Scala file loaded from the file system.
|
||||
*
|
||||
* @param path The path to the file in Scala format.
|
||||
* @return @true when tuning scale loaded OK,
|
||||
* @false if some error occurred.
|
||||
*/
|
||||
bool loadScalaFile(const std::string& path);
|
||||
|
||||
/**
|
||||
* @brief Sets the tuning from a Scala file loaded from memory.
|
||||
*
|
||||
* @param text The contents of the file in Scala format.
|
||||
* @return @true when tuning scale loaded OK,
|
||||
* @false if some error occurred.
|
||||
*/
|
||||
bool loadScalaString(const std::string& text);
|
||||
|
||||
/**
|
||||
* @brief Sets the scala root key.
|
||||
*
|
||||
* @param rootKey The MIDI number of the Scala root key (default 60 for C4).
|
||||
*/
|
||||
void setScalaRootKey(int rootKey);
|
||||
|
||||
/**
|
||||
* @brief Gets the scala root key.
|
||||
*
|
||||
* @return The MIDI number of the Scala root key (default 60 for C4).
|
||||
*/
|
||||
int getScalaRootKey() const;
|
||||
|
||||
/**
|
||||
* @brief Sets the reference tuning frequency.
|
||||
*
|
||||
* @param frequency The frequency which indicates where standard tuning A4 is (default 440 Hz).
|
||||
*/
|
||||
void setTuningFrequency(float frequency);
|
||||
|
||||
/**
|
||||
* @brief Gets the reference tuning frequency.
|
||||
*
|
||||
* @return The frequency which indicates where standard tuning A4 is (default 440 Hz).
|
||||
*/
|
||||
float getTuningFrequency() const;
|
||||
|
||||
/**
|
||||
* @brief Return the current number of regions loaded.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1012,12 +1012,12 @@ void sfz::Region::registerTempo(float secondsPerQuarter) noexcept
|
|||
bpmSwitched = false;
|
||||
}
|
||||
|
||||
float sfz::Region::getBasePitchVariation(int noteNumber, float velocity) const noexcept
|
||||
float sfz::Region::getBasePitchVariation(float noteNumber, float velocity) const noexcept
|
||||
{
|
||||
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
|
||||
|
||||
std::uniform_int_distribution<int> pitchDistribution { -pitchRandom, pitchRandom };
|
||||
auto pitchVariationInCents = pitchKeytrack * (noteNumber - (int)pitchKeycenter); // note difference with pitch center
|
||||
auto pitchVariationInCents = pitchKeytrack * (noteNumber - pitchKeycenter); // note difference with pitch center
|
||||
pitchVariationInCents += tune; // sample tuning
|
||||
pitchVariationInCents += config::centPerSemitone * transpose; // sample transpose
|
||||
pitchVariationInCents += static_cast<int>(velocity) * pitchVeltrack; // track velocity
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ struct Region {
|
|||
* @param velocity
|
||||
* @return float
|
||||
*/
|
||||
float getBasePitchVariation(int noteNumber, float velocity) const noexcept;
|
||||
float getBasePitchVariation(float noteNumber, float velocity) const noexcept;
|
||||
/**
|
||||
* @brief Get the note-related gain of the region depending on which note has been
|
||||
* pressed and at which velocity.
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
#include "Logger.h"
|
||||
#include "Wavetables.h"
|
||||
#include "Curve.h"
|
||||
#include "Tuning.h"
|
||||
|
||||
namespace sfz
|
||||
{
|
||||
|
|
@ -27,6 +28,7 @@ struct Resources
|
|||
FilterPool filterPool { midiState };
|
||||
EQPool eqPool { midiState };
|
||||
WavetablePool wavePool;
|
||||
Tuning tuning;
|
||||
|
||||
void setSampleRate(float samplerate)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -504,6 +504,36 @@ void sfz::Synth::finalizeSfzLoad()
|
|||
}
|
||||
}
|
||||
|
||||
bool sfz::Synth::loadScalaFile(const fs::path& path)
|
||||
{
|
||||
return resources.tuning.loadScalaFile(path);
|
||||
}
|
||||
|
||||
bool sfz::Synth::loadScalaString(const std::string& text)
|
||||
{
|
||||
return resources.tuning.loadScalaString(text);
|
||||
}
|
||||
|
||||
void sfz::Synth::setScalaRootKey(int rootKey)
|
||||
{
|
||||
resources.tuning.setScalaRootKey(rootKey);
|
||||
}
|
||||
|
||||
int sfz::Synth::getScalaRootKey() const
|
||||
{
|
||||
return resources.tuning.getScalaRootKey();
|
||||
}
|
||||
|
||||
void sfz::Synth::setTuningFrequency(float frequency)
|
||||
{
|
||||
resources.tuning.setTuningFrequency(frequency);
|
||||
}
|
||||
|
||||
float sfz::Synth::getTuningFrequency() const
|
||||
{
|
||||
return resources.tuning.getTuningFrequency();
|
||||
}
|
||||
|
||||
sfz::Voice* sfz::Synth::findFreeVoice() noexcept
|
||||
{
|
||||
auto freeVoice = absl::c_find_if(voices, [](const std::unique_ptr<Voice>& voice) {
|
||||
|
|
|
|||
|
|
@ -110,6 +110,46 @@ public:
|
|||
* parsing step.
|
||||
*/
|
||||
void finalizeSfzLoad();
|
||||
/**
|
||||
* @brief Sets the tuning from a Scala file loaded from the file system.
|
||||
*
|
||||
* @param path The path to the file in Scala format.
|
||||
* @return @true when tuning scale loaded OK,
|
||||
* @false if some error occurred.
|
||||
*/
|
||||
bool loadScalaFile(const fs::path& path);
|
||||
/**
|
||||
* @brief Sets the tuning from a Scala file loaded from memory.
|
||||
*
|
||||
* @param text The contents of the file in Scala format.
|
||||
* @return @true when tuning scale loaded OK,
|
||||
* @false if some error occurred.
|
||||
*/
|
||||
bool loadScalaString(const std::string& text);
|
||||
/**
|
||||
* @brief Sets the scala root key.
|
||||
*
|
||||
* @param rootKey The MIDI number of the Scala root key (default 60 for C4).
|
||||
*/
|
||||
void setScalaRootKey(int rootKey);
|
||||
/**
|
||||
* @brief Gets the scala root key.
|
||||
*
|
||||
* @return The MIDI number of the Scala root key (default 60 for C4).
|
||||
*/
|
||||
int getScalaRootKey() const;
|
||||
/**
|
||||
* @brief Sets the reference tuning frequency.
|
||||
*
|
||||
* @param frequency The frequency which indicates where standard tuning A4 is (default 440 Hz).
|
||||
*/
|
||||
void setTuningFrequency(float frequency);
|
||||
/**
|
||||
* @brief Gets the reference tuning frequency.
|
||||
*
|
||||
* @return The frequency which indicates where standard tuning A4 is (default 440 Hz).
|
||||
*/
|
||||
float getTuningFrequency() const;
|
||||
/**
|
||||
* @brief Get the current number of regions loaded
|
||||
*
|
||||
|
|
|
|||
202
src/sfizz/Tuning.cpp
Normal file
202
src/sfizz/Tuning.cpp
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
// This code is part of the sfizz library and is licensed under a BSD 2-clause
|
||||
// license. You should have receive a LICENSE.md file along with the code.
|
||||
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
|
||||
|
||||
#include "Tuning.h"
|
||||
#include "Debug.h"
|
||||
#include "Tunings.h" // Surge tuning library
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
|
||||
namespace sfz {
|
||||
|
||||
struct Tuning::Impl {
|
||||
public:
|
||||
Impl() { updateKeysFractional12TET(); }
|
||||
|
||||
const Tunings::Tuning& tuning() const { return tuning_; }
|
||||
|
||||
float getKeyFractional12TET(int midiKey) const;
|
||||
|
||||
int rootKey() const { return rootKey_; }
|
||||
float tuningFrequency() const { return tuningFrequency_; }
|
||||
|
||||
void updateScale(const Tunings::Scale& scale);
|
||||
void updateRootKey(int rootKey);
|
||||
void updateTuningFrequency(float tuningFrequency);
|
||||
|
||||
private:
|
||||
void updateKeysFractional12TET();
|
||||
static Tunings::KeyboardMapping mappingFromParameters(int rootKey, float tuningFrequency);
|
||||
|
||||
private:
|
||||
static constexpr int defaultRootKey = 60;
|
||||
static constexpr float defaultTuningFrequency = 440.0;
|
||||
int rootKey_ = defaultRootKey;
|
||||
float tuningFrequency_ = defaultTuningFrequency;
|
||||
|
||||
Tunings::Tuning tuning_ {
|
||||
Tunings::evenTemperament12NoteScale(),
|
||||
mappingFromParameters(defaultRootKey, defaultTuningFrequency)
|
||||
};
|
||||
|
||||
static constexpr int numKeys = Tunings::Tuning::N;
|
||||
std::array<float, numKeys> keysFractional12TET_;
|
||||
};
|
||||
|
||||
///
|
||||
constexpr int Tuning::Impl::defaultRootKey;
|
||||
constexpr float Tuning::Impl::defaultTuningFrequency;
|
||||
constexpr int Tuning::Impl::numKeys;
|
||||
|
||||
float Tuning::Impl::getKeyFractional12TET(int midiKey) const
|
||||
{
|
||||
return keysFractional12TET_[std::max(0, std::min(numKeys - 1, midiKey))];
|
||||
}
|
||||
|
||||
void Tuning::Impl::updateScale(const Tunings::Scale& scale)
|
||||
{
|
||||
tuning_ = Tunings::Tuning(scale, tuning_.keyboardMapping);
|
||||
updateKeysFractional12TET();
|
||||
}
|
||||
|
||||
void Tuning::Impl::updateRootKey(int rootKey)
|
||||
{
|
||||
ASSERT(rootKey >= 0);
|
||||
rootKey = std::max(0, rootKey);
|
||||
|
||||
if (rootKey_ == rootKey)
|
||||
return;
|
||||
|
||||
tuning_ = Tunings::Tuning(tuning_.scale, mappingFromParameters(rootKey, tuningFrequency_));
|
||||
rootKey_ = rootKey;
|
||||
updateKeysFractional12TET();
|
||||
}
|
||||
|
||||
void Tuning::Impl::updateTuningFrequency(float tuningFrequency)
|
||||
{
|
||||
ASSERT(tuningFrequency >= 0);
|
||||
tuningFrequency = std::max(0.0f, tuningFrequency);
|
||||
|
||||
if (tuningFrequency_ == tuningFrequency)
|
||||
return;
|
||||
|
||||
tuning_ = Tunings::Tuning(tuning_.scale, mappingFromParameters(rootKey_, tuningFrequency));
|
||||
tuningFrequency_ = tuningFrequency;
|
||||
updateKeysFractional12TET();
|
||||
}
|
||||
|
||||
void Tuning::Impl::updateKeysFractional12TET()
|
||||
{
|
||||
// mapping of MIDI key to equal temperament key
|
||||
for (int key = 0; key < numKeys; ++key) {
|
||||
double freq = tuning_.frequencyForMidiNote(key);
|
||||
keysFractional12TET_[key] = 12.0 * std::log2(freq / 440.0) + 69.0;
|
||||
}
|
||||
}
|
||||
|
||||
Tunings::KeyboardMapping Tuning::Impl::mappingFromParameters(int rootKey, float tuningFrequency)
|
||||
{
|
||||
#if 1
|
||||
// root note is the start of octave. like Scala
|
||||
#else
|
||||
// root note is the start of next octave. like Sforzando
|
||||
rootKey = std::max(0, rootKey - 12);
|
||||
#endif
|
||||
// fixed frequency of the root note
|
||||
const double rootFrequency = tuningFrequency * std::exp2((rootKey - 69) / 12.0);
|
||||
return Tunings::tuneNoteTo(rootKey, rootFrequency);
|
||||
}
|
||||
|
||||
///
|
||||
Tuning::Tuning()
|
||||
: impl_(new Impl)
|
||||
{
|
||||
}
|
||||
|
||||
Tuning::~Tuning()
|
||||
{
|
||||
}
|
||||
|
||||
bool Tuning::loadScalaFile(const fs::path& path)
|
||||
{
|
||||
const Tunings::KeyboardMapping kbm = impl_->tuning().keyboardMapping;
|
||||
|
||||
fs::ifstream stream(path);
|
||||
if (stream.bad()) {
|
||||
DBG("Cannot open scale file: " << path);
|
||||
return false;
|
||||
}
|
||||
|
||||
Tunings::Scale scl;
|
||||
try {
|
||||
scl = Tunings::readSCLStream(stream);
|
||||
}
|
||||
catch (Tunings::TuningError& error) {
|
||||
DBG("Tuning: " << error.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
impl_->updateScale(scl);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Tuning::loadScalaString(const std::string& text)
|
||||
{
|
||||
const Tunings::KeyboardMapping kbm = impl_->tuning().keyboardMapping;
|
||||
|
||||
std::istringstream stream(text);
|
||||
|
||||
Tunings::Scale scl;
|
||||
try {
|
||||
scl = Tunings::readSCLStream(stream);
|
||||
}
|
||||
catch (Tunings::TuningError& error) {
|
||||
DBG("Tuning: " << error.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
impl_->updateScale(scl);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Tuning::setScalaRootKey(int rootKey)
|
||||
{
|
||||
impl_->updateRootKey(rootKey);
|
||||
}
|
||||
|
||||
int Tuning::getScalaRootKey() const
|
||||
{
|
||||
return impl_->rootKey();
|
||||
}
|
||||
|
||||
void Tuning::setTuningFrequency(float frequency)
|
||||
{
|
||||
impl_->updateTuningFrequency(frequency);
|
||||
}
|
||||
|
||||
float Tuning::getTuningFrequency() const
|
||||
{
|
||||
return impl_->tuningFrequency();
|
||||
}
|
||||
|
||||
void Tuning::loadEqualTemperamentScale()
|
||||
{
|
||||
impl_->updateScale(Tunings::evenTemperament12NoteScale());
|
||||
}
|
||||
|
||||
float Tuning::getFrequencyOfKey(int midiKey) const
|
||||
{
|
||||
return impl_->tuning().frequencyForMidiNote(midiKey);
|
||||
}
|
||||
|
||||
float Tuning::getKeyFractional12TET(int midiKey)
|
||||
{
|
||||
return impl_->getKeyFractional12TET(midiKey);
|
||||
}
|
||||
|
||||
} // namespace sfz
|
||||
68
src/sfizz/Tuning.h
Normal file
68
src/sfizz/Tuning.h
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
// This code is part of the sfizz library and is licensed under a BSD 2-clause
|
||||
// license. You should have receive a LICENSE.md file along with the code.
|
||||
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
|
||||
|
||||
#pragma once
|
||||
#include "ghc/fs_std.hpp"
|
||||
#include <memory>
|
||||
|
||||
namespace sfz {
|
||||
|
||||
class Tuning {
|
||||
public:
|
||||
Tuning();
|
||||
~Tuning();
|
||||
|
||||
/**
|
||||
* @brief Load a scale from a file in the Scala format.
|
||||
*/
|
||||
bool loadScalaFile(const fs::path& path);
|
||||
|
||||
/**
|
||||
* @brief Load a scale from memory in the Scala format.
|
||||
*/
|
||||
bool loadScalaString(const std::string& text);
|
||||
|
||||
/**
|
||||
* @brief Set the root key.
|
||||
*/
|
||||
void setScalaRootKey(int rootKey);
|
||||
|
||||
/**
|
||||
* @brief Get the root key.
|
||||
*/
|
||||
int getScalaRootKey() const;
|
||||
|
||||
/**
|
||||
* @brief Set the tuning frequency.
|
||||
*/
|
||||
void setTuningFrequency(float frequency);
|
||||
|
||||
/**
|
||||
* @brief Get the tuning frequency.
|
||||
*/
|
||||
float getTuningFrequency() const;
|
||||
|
||||
/**
|
||||
* @brief Load the equal-temperament scale.
|
||||
*/
|
||||
void loadEqualTemperamentScale();
|
||||
|
||||
/**
|
||||
* @brief Get the MIDI key frequency under the present tuning.
|
||||
*/
|
||||
float getFrequencyOfKey(int midiKey) const;
|
||||
|
||||
/**
|
||||
* @brief Get the fractional MIDI key reconverted into equal temperament scale.
|
||||
*/
|
||||
float getKeyFractional12TET(int midiKey);
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> impl_;
|
||||
};
|
||||
|
||||
} // namespace sfz
|
||||
|
|
@ -81,7 +81,11 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, float value,
|
|||
}
|
||||
speedRatio = static_cast<float>(currentPromise->sampleRate / this->sampleRate);
|
||||
}
|
||||
pitchRatio = region->getBasePitchVariation(number, value);
|
||||
|
||||
// do Scala retuning and reconvert the frequency into a 12TET key number
|
||||
const float numberRetuned = resources.tuning.getKeyFractional12TET(number);
|
||||
|
||||
pitchRatio = region->getBasePitchVariation(numberRetuned, value);
|
||||
baseVolumedB = region->getBaseVolumedB(number);
|
||||
baseGain = region->getBaseGain();
|
||||
if (triggerType != TriggerType::CC)
|
||||
|
|
@ -107,7 +111,7 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, float value,
|
|||
sourcePosition = region->getOffset();
|
||||
triggerDelay = delay;
|
||||
initialDelay = delay + static_cast<int>(region->getDelay() * sampleRate);
|
||||
baseFrequency = midiNoteFrequency(number);
|
||||
baseFrequency = resources.tuning.getFrequencyOfKey(number);
|
||||
bendStepFactor = centsFactor(region->bendStep);
|
||||
egEnvelope.reset(region->amplitudeEG, *region, resources.midiState, delay, value, sampleRate);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,36 @@ bool sfz::Sfizz::loadSfzString(const std::string& path, const std::string& text)
|
|||
return synth->loadSfzString(path, text);
|
||||
}
|
||||
|
||||
bool sfz::Sfizz::loadScalaFile(const std::string& path)
|
||||
{
|
||||
return synth->loadScalaFile(path);
|
||||
}
|
||||
|
||||
bool sfz::Sfizz::loadScalaString(const std::string& text)
|
||||
{
|
||||
return synth->loadScalaString(text);
|
||||
}
|
||||
|
||||
void sfz::Sfizz::setScalaRootKey(int rootKey)
|
||||
{
|
||||
return synth->setScalaRootKey(rootKey);
|
||||
}
|
||||
|
||||
int sfz::Sfizz::getScalaRootKey() const
|
||||
{
|
||||
return synth->getScalaRootKey();
|
||||
}
|
||||
|
||||
void sfz::Sfizz::setTuningFrequency(float frequency)
|
||||
{
|
||||
return synth->setTuningFrequency(frequency);
|
||||
}
|
||||
|
||||
float sfz::Sfizz::getTuningFrequency() const
|
||||
{
|
||||
return synth->getTuningFrequency();
|
||||
}
|
||||
|
||||
int sfz::Sfizz::getNumRegions() const noexcept
|
||||
{
|
||||
return synth->getNumRegions();
|
||||
|
|
|
|||
|
|
@ -31,6 +31,42 @@ bool sfizz_load_string(sfizz_synth_t* synth, const char* path, const char* text)
|
|||
return self->loadSfzString(path, text);
|
||||
}
|
||||
|
||||
bool sfizz_load_scala_file(sfizz_synth_t* synth, const char* path)
|
||||
{
|
||||
auto self = reinterpret_cast<sfz::Synth*>(synth);
|
||||
return self->loadScalaFile(path);
|
||||
}
|
||||
|
||||
bool sfizz_load_scala_string(sfizz_synth_t* synth, const char* text)
|
||||
{
|
||||
auto self = reinterpret_cast<sfz::Synth*>(synth);
|
||||
return self->loadScalaString(text);
|
||||
}
|
||||
|
||||
void sfizz_set_scala_root_key(sfizz_synth_t* synth, int root_key)
|
||||
{
|
||||
auto self = reinterpret_cast<sfz::Synth*>(synth);
|
||||
self->setScalaRootKey(root_key);
|
||||
}
|
||||
|
||||
int sfizz_get_scala_root_key(sfizz_synth_t* synth)
|
||||
{
|
||||
auto self = reinterpret_cast<sfz::Synth*>(synth);
|
||||
return self->getScalaRootKey();
|
||||
}
|
||||
|
||||
void sfizz_set_tuning_frequency(sfizz_synth_t* synth, float frequency)
|
||||
{
|
||||
auto self = reinterpret_cast<sfz::Synth*>(synth);
|
||||
self->setTuningFrequency(frequency);
|
||||
}
|
||||
|
||||
float sfizz_get_tuning_frequency(sfizz_synth_t* synth)
|
||||
{
|
||||
auto self = reinterpret_cast<sfz::Synth*>(synth);
|
||||
return self->getTuningFrequency();
|
||||
}
|
||||
|
||||
void sfizz_free(sfizz_synth_t* synth)
|
||||
{
|
||||
delete reinterpret_cast<sfz::Synth*>(synth);
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ set(SFIZZ_TEST_SOURCES
|
|||
FloatHelpersT.cpp
|
||||
WavetablesT.cpp
|
||||
SemaphoreT.cpp
|
||||
TuningT.cpp
|
||||
)
|
||||
|
||||
add_executable(sfizz_tests ${SFIZZ_TEST_SOURCES})
|
||||
|
|
@ -79,4 +80,7 @@ target_link_libraries(sfizz_plot_wavetables PRIVATE sfizz::sfizz)
|
|||
add_executable(sfizz_file_instrument FileInstrument.cpp)
|
||||
target_link_libraries(sfizz_file_instrument PRIVATE sfizz::sfizz)
|
||||
|
||||
add_executable(sfizz_tuning Tuning.cpp)
|
||||
target_link_libraries(sfizz_tuning PRIVATE sfizz::sfizz)
|
||||
|
||||
file(COPY "." DESTINATION ${CMAKE_BINARY_DIR}/tests)
|
||||
|
|
|
|||
106
tests/Tuning.cpp
Normal file
106
tests/Tuning.cpp
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
// This code is part of the sfizz library and is licensed under a BSD 2-clause
|
||||
// license. You should have receive a LICENSE.md file along with the code.
|
||||
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
|
||||
|
||||
#include "sfizz/Tuning.h"
|
||||
#include "sfizz/SfzHelpers.h"
|
||||
#include "cxxopts.hpp"
|
||||
#include <string>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <cmath>
|
||||
|
||||
static const char *octNoteNames[12] = {
|
||||
"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B",
|
||||
};
|
||||
|
||||
static std::string noteName(int key)
|
||||
{
|
||||
int octNum;
|
||||
int octNoteNum;
|
||||
if (key >= 0) {
|
||||
octNum = key / 12 - 1;
|
||||
octNoteNum = key % 12;
|
||||
}
|
||||
else {
|
||||
octNum = -2 - (key + 1) / -12;
|
||||
octNoteNum = (key % 12 + 12) % 12;
|
||||
}
|
||||
return std::string(octNoteNames[octNoteNum]) + std::to_string(octNum);
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
cxxopts::Options options(argv[0], " - command line options");
|
||||
|
||||
options.add_options()
|
||||
("h,help", "Print help")
|
||||
("s,scale", "Path of scala tuning file", cxxopts::value<std::string>())
|
||||
("f,frequency", "Tuning frequency", cxxopts::value<float>()->default_value("440.0"))
|
||||
("r,root-key", "Root key", cxxopts::value<std::string>()->default_value("C5"));
|
||||
|
||||
auto result = options.parse(argc, argv);
|
||||
|
||||
if (result.count("help")) {
|
||||
std::cout << options.help({""}) << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
///
|
||||
sfz::Tuning tuning;
|
||||
|
||||
if (result.count("scale")) {
|
||||
if (!tuning.loadScalaFile(result["scale"].as<std::string>())) {
|
||||
fprintf(stderr, "Could not load the scale file.\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
absl::optional<uint8_t> noteNumber = sfz::readNoteValue(
|
||||
result["root-key"].as<std::string>());
|
||||
if (!noteNumber) {
|
||||
fprintf(stderr, "The root key is not a valid note name.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
tuning.setScalaRootKey(*noteNumber);
|
||||
tuning.setTuningFrequency(result["frequency"].as<float>());
|
||||
|
||||
const int numRows = 3;
|
||||
const int numCols = 4;
|
||||
|
||||
for (int row = 0; row < numRows; ++row) {
|
||||
for (int i = 0; i < 73; ++i)
|
||||
putchar('-');
|
||||
putchar('\n');
|
||||
for (int nthKey = 0; nthKey < 12; ++nthKey) {
|
||||
for (int col = 0; col < numCols; ++col) {
|
||||
const int key = nthKey + (col + row * numCols) * 12;
|
||||
|
||||
std::string label;
|
||||
label.push_back('|');
|
||||
label.append(noteName(key));
|
||||
while (label.size() < 5)
|
||||
label.push_back(' ');
|
||||
label.push_back('|');
|
||||
|
||||
if (col > 0) {
|
||||
for (int i = 0; i < 1; ++i)
|
||||
putchar(' ');
|
||||
}
|
||||
|
||||
printf("%s %10.4f", label.c_str(), tuning.getFrequencyOfKey(key));
|
||||
}
|
||||
putchar(' ');
|
||||
putchar('|');
|
||||
putchar('\n');
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < 73; ++i)
|
||||
putchar('-');
|
||||
putchar('\n');
|
||||
|
||||
return 0;
|
||||
}
|
||||
17
tests/TuningT.cpp
Normal file
17
tests/TuningT.cpp
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
// This code is part of the sfizz library and is licensed under a BSD 2-clause
|
||||
// license. You should have receive a LICENSE.md file along with the code.
|
||||
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
|
||||
|
||||
#include "sfizz/Tuning.h"
|
||||
#include "sfizz/MathHelpers.h"
|
||||
#include "catch2/catch.hpp"
|
||||
|
||||
TEST_CASE("[Tuning] Default tuning")
|
||||
{
|
||||
sfz::Tuning defaultTuning;
|
||||
|
||||
for (int key = 0; key < 128; ++key)
|
||||
REQUIRE(defaultTuning.getFrequencyOfKey(key) == Approx(midiNoteFrequency(key)));
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue