Merge pull request #761 from paulfd/osc-region-change

Updating regions through OSC
This commit is contained in:
Paul Ferrand 2021-04-05 00:15:07 +02:00 committed by GitHub
commit 254d00768e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 247 additions and 17 deletions

View file

@ -53,9 +53,9 @@ sfz::Region::Region(int regionNumber, absl::string_view defaultPath)
case hash(x "_stepcc&"): \
case hash(x "_smoothcc&")
bool sfz::Region::parseOpcode(const Opcode& rawOpcode)
bool sfz::Region::parseOpcode(const Opcode& rawOpcode, bool cleanOpcode)
{
const Opcode opcode = rawOpcode.cleanUp(kOpcodeScopeRegion);
const Opcode opcode = cleanOpcode ? rawOpcode.cleanUp(kOpcodeScopeRegion) : rawOpcode;
switch (opcode.lettersOnlyHash) {

View file

@ -186,10 +186,11 @@ struct Region {
* This must be called multiple times for each opcode applying to this region.
*
* @param opcode
* @param cleanOpcode whether the opcode should be canonicalized
* @return true if the opcode was properly read and stored.
* @return false
*/
bool parseOpcode(const Opcode& opcode);
bool parseOpcode(const Opcode& opcode, bool cleanOpcode = true);
/**
* @brief Parse a opcode which is specific to a particular SFZv1 LFO:
* amplfo, pitchlfo, fillfo.

View file

@ -896,6 +896,26 @@ void Synth::setSampleRate(float sampleRate) noexcept
}
}
void Synth::Impl::updateRegions() noexcept
{
std::unique_lock<SpinMutex> lock { regionUpdatesMutex_, std::try_to_lock };
if (!lock.owns_lock())
return;
absl::c_sort(regionUpdates_, [](const OpcodeUpdate& lhs, const OpcodeUpdate& rhs) {
return lhs.delay < rhs.delay;
});
for (auto& update: regionUpdates_) {
if (!update.region)
continue;
update.region->parseOpcode(update.opcode, false);
}
regionUpdates_.clear();
}
void Synth::renderBlock(AudioSpan<float> buffer) noexcept
{
Impl& impl = *impl_;
@ -925,6 +945,8 @@ void Synth::renderBlock(AudioSpan<float> buffer) noexcept
impl.resources_.filePool.triggerGarbageCollection();
}
impl.updateRegions();
auto tempSpan = impl.resources_.bufferPool.getStereoBuffer(numFrames);
auto tempMixSpan = impl.resources_.bufferPool.getStereoBuffer(numFrames);
auto rampSpan = impl.resources_.bufferPool.getBuffer(numFrames);

View file

@ -33,6 +33,21 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co
Layer& layer = *impl.layers_[idx]; \
const Region& region = layer.getRegion();
#define GET_FILTER_OR_BREAK(idx) \
if (idx >= region.filters.size()) \
break; \
const auto& filter = region.filters[idx];
#define GET_EQ_OR_BREAK(idx) \
if (idx >= region.equalizers.size()) \
break; \
const auto& eq = region.equalizers[idx];
#define GET_LFO_OR_BREAK(idx) \
if (idx >= region.lfos.size()) \
break; \
const auto& lfo = region.lfos[idx];
MATCH("/hello", "") {
client.receive(delay, "/hello", "", nullptr);
} break;
@ -1213,11 +1228,6 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co
client.receive<'f'>(delay, path, value * 100.0f);
} break;
#define GET_FILTER_OR_BREAK(idx) \
if (idx >= region.filters.size()) \
break; \
const auto& filter = region.filters[idx];
MATCH("/region&/filter&/cutoff", "") {
GET_REGION_OR_BREAK(indices[0])
GET_FILTER_OR_BREAK(indices[1])
@ -1285,13 +1295,6 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co
}
} break;
#undef GET_FILTER_OR_BREAK
#define GET_EQ_OR_BREAK(idx) \
if (idx >= region.equalizers.size()) \
break; \
const auto& eq = region.equalizers[idx];
MATCH("/region&/eq&/gain", "") {
GET_REGION_OR_BREAK(indices[0])
GET_EQ_OR_BREAK(indices[1])
@ -1337,9 +1340,72 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co
}
} break;
#undef GET_EQ_OR_BREAK
MATCH("/region&/lfo&/wave", "") {
GET_REGION_OR_BREAK(indices[0])
GET_LFO_OR_BREAK(indices[1])
if (lfo.sub.size() == 0)
break;
client.receive<'i'>(delay, path, static_cast<int32_t>(lfo.sub[0].wave));
} break;
#undef GET_REGION_OR_BREAK
#undef GET_FILTER_OR_BREAK
#undef GET_EQ_OR_BREAK
#undef GET_LFO_OR_BREAK
//----------------------------------------------------------------------
// Setting values
// Note: all these must be rt-safe within the parseOpcode method in region
#define GET_REGION_OR_BREAK(idx) \
if (idx >= impl.layers_.size()) \
break; \
Layer& layer = *impl.layers_[idx]; \
Region& region = layer.getRegion();
MATCH("/region&/pitch_keycenter", "i") {
GET_REGION_OR_BREAK(indices[0])
std::lock_guard<SpinMutex> lock { impl.regionUpdatesMutex_ };
Impl::OpcodeUpdate update { delay, &region,
Opcode { "pitch_keycenter", std::to_string(args[0].i) } };
impl.regionUpdates_.emplace_back(update);
} break;
MATCH("/region&/loop_mode", "s") {
GET_REGION_OR_BREAK(indices[0])
std::lock_guard<SpinMutex> lock { impl.regionUpdatesMutex_ };
Impl::OpcodeUpdate update { delay, &region,
Opcode { "loop_mode", args[0].s } };
impl.regionUpdates_.emplace_back(update);
} break;
MATCH("/region&/filter&/type", "s") {
GET_REGION_OR_BREAK(indices[0])
if (indices[1] >= region.filters.size())
break;
std::lock_guard<SpinMutex> lock { impl.regionUpdatesMutex_ };
Impl::OpcodeUpdate update { delay, &region,
Opcode { absl::StrCat("fil", indices[1] + 1 , "_type "), args[0].s } };
impl.regionUpdates_.emplace_back(update);
} break;
MATCH("/region&/lfo&/wave", "i") {
GET_REGION_OR_BREAK(indices[0])
if (indices[1] >= region.lfos.size())
break;
std::lock_guard<SpinMutex> lock { impl.regionUpdatesMutex_ };
Impl::OpcodeUpdate update { delay, &region,
Opcode { absl::StrCat("lfo", indices[1] + 1, "_wave1"), std::to_string(args[0].i) } };
impl.regionUpdates_.emplace_back(update);
} break;
#undef GET_REGION_OR_BREAK
//----------------------------------------------------------------------
// Voices
MATCH("/num_active_voices", "") {
client.receive<'i'>(delay, path, impl.voiceManager_.getNumActiveVoices());

View file

@ -180,6 +180,12 @@ struct Synth::Impl final: public Parser::Listener {
*/
void finalizeSfzLoad();
/**
* @brief Update the regions with the opcodes received through OSC if necessary
*
*/
void updateRegions() noexcept;
template<class T>
static void collectUsedCCsFromCCMap(BitArray<config::numCCs>& usedCCs, const CCMap<T> map) noexcept
{
@ -343,6 +349,16 @@ struct Synth::Impl final: public Parser::Listener {
}
bool playheadMoved_ { false };
struct OpcodeUpdate
{
int delay;
Region* region;
Opcode opcode;
};
std::vector<OpcodeUpdate> regionUpdates_;
SpinMutex regionUpdatesMutex_;
};
} // namespace sfz

View file

@ -223,6 +223,7 @@ struct Voice::Impl
float baseVolumedB_ { 0.0 };
float baseGain_ { 1.0 };
float baseFrequency_ { 440.0 };
uint8_t pitchKeycenter_ { Default::key };
float floatPositionOffset_ { 0.0f };
int sourcePosition_ { 0 };
@ -472,6 +473,7 @@ bool Voice::startVoice(Layer* layer, int delay, const TriggerEvent& event) noexc
if (resources.stretch)
impl.pitchRatio_ *= resources.stretch->getRatioForFractionalKey(numberRetuned);
impl.pitchKeycenter_ = region.pitchKeycenter;
impl.baseVolumedB_ = region.getBaseVolumedB(resources.midiState, impl.triggerEvent_.number);
impl.baseGain_ = region.getBaseGain();
if (impl.triggerEvent_.type != TriggerEventType::CC || region.velocityOverride == VelocityOverride::previous)
@ -1449,7 +1451,7 @@ void Voice::Impl::fillWithGenerator(AudioSpan<float> buffer) noexcept
if (!frequencies)
return;
float keycenterFrequency = midiNoteFrequency(region_->pitchKeycenter);
float keycenterFrequency = midiNoteFrequency(pitchKeycenter_);
fill(*frequencies, pitchRatio_ * keycenterFrequency);
pitchEnvelope(*frequencies);

View file

@ -9,6 +9,7 @@ include(Catch)
set(SFIZZ_TEST_SOURCES
DirectRegionT.cpp
RegionValuesT.cpp
RegionValuesSetT.cpp
TestHelpers.h
TestHelpers.cpp
ParsingT.cpp

122
tests/RegionValuesSetT.cpp Normal file
View file

@ -0,0 +1,122 @@
// 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 "TestHelpers.h"
#include "sfizz/Synth.h"
#include "catch2/catch.hpp"
#include <stdexcept>
#include <absl/strings/str_cat.h>
#include <absl/strings/str_format.h>
using namespace Catch::literals;
using namespace sfz;
TEST_CASE("[Set values] Pitch keycenter")
{
Synth synth;
std::vector<std::string> messageList;
Client client(&messageList);
client.setReceiveCallback(&simpleMessageReceiver);
AudioBuffer<float> buffer { 2, 256 };
synth.loadSfzString(fs::current_path() / "tests/TestFiles/values_set.sfz", R"(
<region> sample=*sine pitch_keycenter=48
)");
synth.dispatchMessage(client, 0, "/region0/pitch_keycenter", "", nullptr);
// Update value
sfizz_arg_t args;
args.i = 60;
synth.dispatchMessage(client, 1, "/region0/pitch_keycenter", "i", &args);
synth.renderBlock(buffer);
synth.dispatchMessage(client, 0, "/region0/pitch_keycenter", "", nullptr);
std::vector<std::string> expected {
"/region0/pitch_keycenter,i : { 48 }",
"/region0/pitch_keycenter,i : { 60 }",
};
REQUIRE(messageList == expected);
}
TEST_CASE("[Set values] LFO wave")
{
Synth synth;
std::vector<std::string> messageList;
Client client(&messageList);
client.setReceiveCallback(&simpleMessageReceiver);
AudioBuffer<float> buffer { 2, 256 };
synth.loadSfzString(fs::current_path() / "tests/TestFiles/values_set.sfz", R"(
<region> sample=*sine lfo1_wave=5
)");
synth.dispatchMessage(client, 0, "/region0/lfo0/wave", "", nullptr);
// Update value
sfizz_arg_t args;
args.i = 2;
synth.dispatchMessage(client, 1, "/region0/lfo0/wave", "i", &args);
synth.renderBlock(buffer);
synth.dispatchMessage(client, 0, "/region0/lfo0/wave", "", nullptr);
std::vector<std::string> expected {
"/region0/lfo0/wave,i : { 5 }",
"/region0/lfo0/wave,i : { 2 }",
};
REQUIRE(messageList == expected);
}
TEST_CASE("[Set values] Filter type")
{
Synth synth;
std::vector<std::string> messageList;
Client client(&messageList);
client.setReceiveCallback(&simpleMessageReceiver);
AudioBuffer<float> buffer { 2, 256 };
synth.loadSfzString(fs::current_path() / "tests/TestFiles/values_set.sfz", R"(
<region> sample=*sine fil2_type=lpf_1p
)");
synth.dispatchMessage(client, 0, "/region0/filter1/type", "", nullptr);
// Update value
sfizz_arg_t args;
args.s = "hpf_2p";
synth.dispatchMessage(client, 1, "/region0/filter1/type", "s", &args);
synth.renderBlock(buffer);
synth.dispatchMessage(client, 0, "/region0/filter1/type", "", nullptr);
std::vector<std::string> expected {
"/region0/filter1/type,s : { lpf_1p }",
"/region0/filter1/type,s : { hpf_2p }",
};
REQUIRE(messageList == expected);
}
TEST_CASE("[Set values] Loop mode")
{
Synth synth;
std::vector<std::string> messageList;
Client client(&messageList);
client.setReceiveCallback(&simpleMessageReceiver);
AudioBuffer<float> buffer { 2, 256 };
synth.loadSfzString(fs::current_path() / "tests/TestFiles/values_set.sfz", R"(
<region> sample=looped_flute.wav
)");
synth.dispatchMessage(client, 0, "/region0/loop_mode", "", nullptr);
// Update value
sfizz_arg_t args;
args.s = "one_shot";
synth.dispatchMessage(client, 1, "/region0/loop_mode", "s", &args);
synth.renderBlock(buffer);
synth.dispatchMessage(client, 0, "/region0/loop_mode", "", nullptr);
std::vector<std::string> expected {
"/region0/loop_mode,s : { loop_continuous }",
"/region0/loop_mode,s : { one_shot }",
};
REQUIRE(messageList == expected);
}