Add the tuning control

This commit is contained in:
Jean Pierre Cimalando 2020-05-19 14:51:33 +02:00
parent 4b2d265240
commit dfd10a0977
20 changed files with 751 additions and 13 deletions

View file

@ -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
View file

@ -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

View file

@ -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);

View file

@ -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
] .

View file

@ -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()

View file

@ -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.
*

View 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.
*/

View file

@ -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

View file

@ -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.

View file

@ -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)
{

View file

@ -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) {

View file

@ -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
View 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
View 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

View file

@ -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);
}

View file

@ -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();

View file

@ -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);

View file

@ -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
View 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
View 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)));
}