commit
6b01bde1af
9 changed files with 53 additions and 31 deletions
|
|
@ -41,7 +41,7 @@ FloatSpec oscillatorDetuneMod { 0.0f, {-12000.0f, 12000.0f}, kPermissiveBounds }
|
||||||
FloatSpec oscillatorModDepth { 0.0f, {0.0f, 10000.0f}, kNormalizePercent|kPermissiveBounds };
|
FloatSpec oscillatorModDepth { 0.0f, {0.0f, 10000.0f}, kNormalizePercent|kPermissiveBounds };
|
||||||
FloatSpec oscillatorModDepthMod { 0.0f, {0.0f, 10000.0f}, kNormalizePercent|kPermissiveBounds };
|
FloatSpec oscillatorModDepthMod { 0.0f, {0.0f, 10000.0f}, kNormalizePercent|kPermissiveBounds };
|
||||||
Int32Spec oscillatorQuality { 1, {0, 3}, 0 };
|
Int32Spec oscillatorQuality { 1, {0, 3}, 0 };
|
||||||
UInt32Spec group { 0, {0, uint32_t_max}, 0 };
|
Int64Spec group { 0, {-int32_t_max, uint32_t_max}, 0 };
|
||||||
FloatSpec offTime { 6e-3f, {0.0f, 100.0f}, kPermissiveBounds };
|
FloatSpec offTime { 6e-3f, {0.0f, 100.0f}, kPermissiveBounds };
|
||||||
UInt32Spec polyphony { config::maxVoices, {0, config::maxVoices}, kEnforceBounds };
|
UInt32Spec polyphony { config::maxVoices, {0, config::maxVoices}, kEnforceBounds };
|
||||||
UInt32Spec notePolyphony { config::maxVoices, {1, config::maxVoices}, kEnforceBounds };
|
UInt32Spec notePolyphony { config::maxVoices, {1, config::maxVoices}, kEnforceBounds };
|
||||||
|
|
|
||||||
|
|
@ -153,7 +153,7 @@ namespace Default
|
||||||
extern const OpcodeSpec<float> oscillatorModDepth;
|
extern const OpcodeSpec<float> oscillatorModDepth;
|
||||||
extern const OpcodeSpec<float> oscillatorModDepthMod;
|
extern const OpcodeSpec<float> oscillatorModDepthMod;
|
||||||
extern const OpcodeSpec<int32_t> oscillatorQuality;
|
extern const OpcodeSpec<int32_t> oscillatorQuality;
|
||||||
extern const OpcodeSpec<uint32_t> group;
|
extern const OpcodeSpec<int64_t> group;
|
||||||
extern const OpcodeSpec<float> offTime;
|
extern const OpcodeSpec<float> offTime;
|
||||||
extern const OpcodeSpec<uint32_t> polyphony;
|
extern const OpcodeSpec<uint32_t> polyphony;
|
||||||
extern const OpcodeSpec<uint32_t> notePolyphony;
|
extern const OpcodeSpec<uint32_t> notePolyphony;
|
||||||
|
|
|
||||||
|
|
@ -95,8 +95,6 @@ void FlexEnvelope::configure(const FlexEGDescription* desc)
|
||||||
void FlexEnvelope::start(unsigned triggerDelay)
|
void FlexEnvelope::start(unsigned triggerDelay)
|
||||||
{
|
{
|
||||||
Impl& impl = *impl_;
|
Impl& impl = *impl_;
|
||||||
const FlexEGDescription& desc = *impl.desc_;
|
|
||||||
|
|
||||||
impl.delayFramesLeft_ = triggerDelay;
|
impl.delayFramesLeft_ = triggerDelay;
|
||||||
impl.currentFramesUntilRelease_ = absl::nullopt;
|
impl.currentFramesUntilRelease_ = absl::nullopt;
|
||||||
impl.advanceToStage(0);
|
impl.advanceToStage(0);
|
||||||
|
|
@ -116,6 +114,7 @@ void FlexEnvelope::release(unsigned releaseDelay)
|
||||||
|
|
||||||
void FlexEnvelope::cancelRelease(unsigned delay)
|
void FlexEnvelope::cancelRelease(unsigned delay)
|
||||||
{
|
{
|
||||||
|
UNUSED(delay);
|
||||||
Impl& impl = *impl_;
|
Impl& impl = *impl_;
|
||||||
const FlexEGDescription& desc = *impl.desc_;
|
const FlexEGDescription& desc = *impl.desc_;
|
||||||
|
|
||||||
|
|
@ -249,8 +248,6 @@ void FlexEnvelope::Impl::process(absl::Span<float> out)
|
||||||
bool FlexEnvelope::Impl::advanceToStage(unsigned stageNumber)
|
bool FlexEnvelope::Impl::advanceToStage(unsigned stageNumber)
|
||||||
{
|
{
|
||||||
const FlexEGDescription& desc = *desc_;
|
const FlexEGDescription& desc = *desc_;
|
||||||
const MidiState& midiState = resources_->getMidiState();
|
|
||||||
|
|
||||||
currentStageNumber_ = stageNumber;
|
currentStageNumber_ = stageNumber;
|
||||||
|
|
||||||
if (stageNumber >= desc.points.size())
|
if (stageNumber >= desc.points.size())
|
||||||
|
|
|
||||||
|
|
@ -327,8 +327,8 @@ struct Region {
|
||||||
absl::optional<int> oscillatorQuality;
|
absl::optional<int> oscillatorQuality;
|
||||||
|
|
||||||
// Instrument settings: voice lifecycle
|
// Instrument settings: voice lifecycle
|
||||||
uint32_t group { Default::group }; // group
|
int64_t group { Default::group }; // group
|
||||||
absl::optional<uint32_t> offBy {}; // off_by
|
absl::optional<int64_t> offBy {}; // off_by
|
||||||
OffMode offMode { Default::offMode }; // off_mode
|
OffMode offMode { Default::offMode }; // off_mode
|
||||||
float offTime { Default::offTime }; // off_mode
|
float offTime { Default::offTime }; // off_mode
|
||||||
absl::optional<uint32_t> notePolyphony {}; // note_polyphony
|
absl::optional<uint32_t> notePolyphony {}; // note_polyphony
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ void VoiceManager::onVoiceStateChanging(NumericId<Voice> id, Voice::State state)
|
||||||
const uint32_t group = region->group;
|
const uint32_t group = region->group;
|
||||||
RegionSet::removeVoiceFromHierarchy(region, voice);
|
RegionSet::removeVoiceFromHierarchy(region, voice);
|
||||||
swapAndPopFirst(activeVoices_, [voice](const Voice* v) { return v == voice; });
|
swapAndPopFirst(activeVoices_, [voice](const Voice* v) { return v == voice; });
|
||||||
ASSERT(group < polyphonyGroups_.size());
|
ASSERT(polyphonyGroups_.contains(group));
|
||||||
polyphonyGroups_[group].removeVoice(voice);
|
polyphonyGroups_[group].removeVoice(voice);
|
||||||
} else if (state == Voice::State::playing) {
|
} else if (state == Voice::State::playing) {
|
||||||
Voice* voice = getVoiceById(id);
|
Voice* voice = getVoiceById(id);
|
||||||
|
|
@ -27,7 +27,7 @@ void VoiceManager::onVoiceStateChanging(NumericId<Voice> id, Voice::State state)
|
||||||
const uint32_t group = region->group;
|
const uint32_t group = region->group;
|
||||||
activeVoices_.push_back(voice);
|
activeVoices_.push_back(voice);
|
||||||
RegionSet::registerVoiceInHierarchy(region, voice);
|
RegionSet::registerVoiceInHierarchy(region, voice);
|
||||||
ASSERT(group < polyphonyGroups_.size());
|
ASSERT(polyphonyGroups_.contains(group));
|
||||||
polyphonyGroups_[group].registerVoice(voice);
|
polyphonyGroups_[group].registerVoice(voice);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -61,8 +61,7 @@ void VoiceManager::reset()
|
||||||
voice.reset();
|
voice.reset();
|
||||||
|
|
||||||
polyphonyGroups_.clear();
|
polyphonyGroups_.clear();
|
||||||
polyphonyGroups_.emplace_back();
|
polyphonyGroups_.emplace(0, PolyphonyGroup{});
|
||||||
polyphonyGroups_.back().setPolyphonyLimit(config::maxVoices);
|
|
||||||
setStealingAlgorithm(StealingAlgorithm::Oldest);
|
setStealingAlgorithm(StealingAlgorithm::Oldest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -84,29 +83,31 @@ bool VoiceManager::playingAttackVoice(const Region* releaseRegion) noexcept
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VoiceManager::ensureNumPolyphonyGroups(unsigned groupIdx) noexcept
|
void VoiceManager::ensureNumPolyphonyGroups(int groupIdx) noexcept
|
||||||
{
|
{
|
||||||
size_t neededSize = static_cast<size_t>(groupIdx) + 1;
|
if (!polyphonyGroups_.contains(groupIdx))
|
||||||
if (polyphonyGroups_.size() < neededSize)
|
polyphonyGroups_.emplace(groupIdx, PolyphonyGroup{});
|
||||||
polyphonyGroups_.resize(neededSize);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void VoiceManager::setGroupPolyphony(unsigned groupIdx, unsigned polyphony) noexcept
|
void VoiceManager::setGroupPolyphony(int groupIdx, unsigned polyphony) noexcept
|
||||||
{
|
{
|
||||||
ensureNumPolyphonyGroups(groupIdx);
|
ensureNumPolyphonyGroups(groupIdx);
|
||||||
polyphonyGroups_[groupIdx].setPolyphonyLimit(polyphony);
|
polyphonyGroups_[groupIdx].setPolyphonyLimit(polyphony);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const PolyphonyGroup* VoiceManager::getPolyphonyGroupView(int idx) const noexcept
|
const PolyphonyGroup* VoiceManager::getPolyphonyGroupView(int idx) noexcept
|
||||||
{
|
{
|
||||||
return (size_t)idx < polyphonyGroups_.size() ? &polyphonyGroups_[idx] : nullptr;
|
if (!polyphonyGroups_.contains(idx))
|
||||||
|
return {};
|
||||||
|
|
||||||
|
return &polyphonyGroups_[idx];
|
||||||
}
|
}
|
||||||
|
|
||||||
void VoiceManager::clear()
|
void VoiceManager::clear()
|
||||||
{
|
{
|
||||||
for (PolyphonyGroup& pg : polyphonyGroups_)
|
for (auto& pg : polyphonyGroups_)
|
||||||
pg.removeAllVoices();
|
pg.second.removeAllVoices();
|
||||||
list_.clear();
|
list_.clear();
|
||||||
activeVoices_.clear();
|
activeVoices_.clear();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "absl/container/flat_hash_map.h"
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
#include "PolyphonyGroup.h"
|
#include "PolyphonyGroup.h"
|
||||||
#include "Region.h"
|
#include "Region.h"
|
||||||
|
|
@ -59,7 +60,7 @@ struct VoiceManager final : public Voice::StateListener
|
||||||
*
|
*
|
||||||
* @param groupIdx
|
* @param groupIdx
|
||||||
*/
|
*/
|
||||||
void ensureNumPolyphonyGroups(unsigned groupIdx) noexcept;
|
void ensureNumPolyphonyGroups(int groupIdx) noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Set the polyphony for a given group
|
* @brief Set the polyphony for a given group
|
||||||
|
|
@ -69,7 +70,7 @@ struct VoiceManager final : public Voice::StateListener
|
||||||
* @param groupIdx
|
* @param groupIdx
|
||||||
* @param polyphony
|
* @param polyphony
|
||||||
*/
|
*/
|
||||||
void setGroupPolyphony(unsigned groupIdx, unsigned polyphony) noexcept;
|
void setGroupPolyphony(int groupIdx, unsigned polyphony) noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get a view into a given polyphony group
|
* @brief Get a view into a given polyphony group
|
||||||
|
|
@ -77,7 +78,7 @@ struct VoiceManager final : public Voice::StateListener
|
||||||
* @param idx
|
* @param idx
|
||||||
* @return const PolyphonyGroup*
|
* @return const PolyphonyGroup*
|
||||||
*/
|
*/
|
||||||
const PolyphonyGroup* getPolyphonyGroupView(int idx) const noexcept;
|
const PolyphonyGroup* getPolyphonyGroupView(int idx) noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Clear all voices and polyphony groups.
|
* @brief Clear all voices and polyphony groups.
|
||||||
|
|
@ -138,7 +139,7 @@ private:
|
||||||
std::vector<Voice*> activeVoices_;
|
std::vector<Voice*> activeVoices_;
|
||||||
std::vector<Voice*> temp_;
|
std::vector<Voice*> temp_;
|
||||||
// These are the `group=` groups where you can off voices
|
// These are the `group=` groups where you can off voices
|
||||||
std::vector<PolyphonyGroup> polyphonyGroups_;
|
absl::flat_hash_map<int, PolyphonyGroup> polyphonyGroups_;
|
||||||
std::unique_ptr<VoiceStealer> stealer_ { absl::make_unique<OldestStealer>() };
|
std::unique_ptr<VoiceStealer> stealer_ { absl::make_unique<OldestStealer>() };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ TEST_CASE("[Polyphony] Polyphony groups")
|
||||||
<group> group=4
|
<group> group=4
|
||||||
<region> key=62 sample=*sine
|
<region> key=62 sample=*sine
|
||||||
)");
|
)");
|
||||||
REQUIRE( synth.getNumPolyphonyGroups() == 5 );
|
REQUIRE( synth.getNumPolyphonyGroups() == 4 );
|
||||||
REQUIRE( synth.getNumRegions() == 5 );
|
REQUIRE( synth.getNumRegions() == 5 );
|
||||||
REQUIRE( synth.getRegionView(0)->group == 0 );
|
REQUIRE( synth.getRegionView(0)->group == 0 );
|
||||||
REQUIRE( synth.getRegionView(1)->group == 1 );
|
REQUIRE( synth.getRegionView(1)->group == 1 );
|
||||||
|
|
@ -67,7 +67,7 @@ TEST_CASE("[Polyphony] Polyphony groups")
|
||||||
REQUIRE( synth.getRegionView(4)->group == 4 );
|
REQUIRE( synth.getRegionView(4)->group == 4 );
|
||||||
REQUIRE( synth.getPolyphonyGroupView(1)->getPolyphonyLimit() == 3 );
|
REQUIRE( synth.getPolyphonyGroupView(1)->getPolyphonyLimit() == 3 );
|
||||||
REQUIRE( synth.getPolyphonyGroupView(2)->getPolyphonyLimit() == 4 );
|
REQUIRE( synth.getPolyphonyGroupView(2)->getPolyphonyLimit() == 4 );
|
||||||
REQUIRE( synth.getPolyphonyGroupView(3)->getPolyphonyLimit() == sfz::config::maxVoices );
|
REQUIRE( !synth.getPolyphonyGroupView(3) );
|
||||||
REQUIRE( synth.getPolyphonyGroupView(4)->getPolyphonyLimit() == 5 );
|
REQUIRE( synth.getPolyphonyGroupView(4)->getPolyphonyLimit() == 5 );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -426,7 +426,7 @@ TEST_CASE("[Values] Group")
|
||||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"(
|
synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"(
|
||||||
<region> sample=kick.wav
|
<region> sample=kick.wav
|
||||||
<region> sample=kick.wav group=5
|
<region> sample=kick.wav group=5
|
||||||
<region> sample=kick.wav group=-1
|
<region> sample=kick.wav group=-2
|
||||||
)");
|
)");
|
||||||
synth.dispatchMessage(client, 0, "/region0/group", "", nullptr);
|
synth.dispatchMessage(client, 0, "/region0/group", "", nullptr);
|
||||||
synth.dispatchMessage(client, 0, "/region1/group", "", nullptr);
|
synth.dispatchMessage(client, 0, "/region1/group", "", nullptr);
|
||||||
|
|
@ -434,7 +434,7 @@ TEST_CASE("[Values] Group")
|
||||||
std::vector<std::string> expected {
|
std::vector<std::string> expected {
|
||||||
"/region0/group,h : { 0 }",
|
"/region0/group,h : { 0 }",
|
||||||
"/region1/group,h : { 5 }",
|
"/region1/group,h : { 5 }",
|
||||||
"/region2/group,h : { 0 }",
|
"/region2/group,h : { -2 }",
|
||||||
};
|
};
|
||||||
REQUIRE(messageList == expected);
|
REQUIRE(messageList == expected);
|
||||||
}
|
}
|
||||||
|
|
@ -448,7 +448,7 @@ TEST_CASE("[Values] Off by")
|
||||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"(
|
synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"(
|
||||||
<region> sample=kick.wav
|
<region> sample=kick.wav
|
||||||
<region> sample=kick.wav off_by=5
|
<region> sample=kick.wav off_by=5
|
||||||
<region> sample=kick.wav off_by=-1
|
<region> sample=kick.wav off_by=-2
|
||||||
)");
|
)");
|
||||||
synth.dispatchMessage(client, 0, "/region0/off_by", "", nullptr);
|
synth.dispatchMessage(client, 0, "/region0/off_by", "", nullptr);
|
||||||
synth.dispatchMessage(client, 0, "/region1/off_by", "", nullptr);
|
synth.dispatchMessage(client, 0, "/region1/off_by", "", nullptr);
|
||||||
|
|
@ -456,7 +456,7 @@ TEST_CASE("[Values] Off by")
|
||||||
std::vector<std::string> expected {
|
std::vector<std::string> expected {
|
||||||
"/region0/off_by,N : { }",
|
"/region0/off_by,N : { }",
|
||||||
"/region1/off_by,h : { 5 }",
|
"/region1/off_by,h : { 5 }",
|
||||||
"/region2/off_by,N : { }",
|
"/region2/off_by,h : { -2 }",
|
||||||
};
|
};
|
||||||
REQUIRE(messageList == expected);
|
REQUIRE(messageList == expected);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1595,6 +1595,29 @@ TEST_CASE("[Synth] Off by standard")
|
||||||
REQUIRE( playingVoices.front()->getRegion()->keyRange.containsWithEnd(60) );
|
REQUIRE( playingVoices.front()->getRegion()->keyRange.containsWithEnd(60) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Synth] Off by negative groups")
|
||||||
|
{
|
||||||
|
sfz::Synth synth;
|
||||||
|
sfz::AudioBuffer<float> buffer { 2, static_cast<unsigned>(synth.getSamplesPerBlock()) };
|
||||||
|
|
||||||
|
synth.loadSfzString(fs::current_path(), R"(
|
||||||
|
<region> group=-1 off_by=-2 sample=*saw transpose=12 key=60
|
||||||
|
<region> group=-2 off_by=-1 sample=*triangle key=62
|
||||||
|
)");
|
||||||
|
synth.noteOn(0, 60, 85);
|
||||||
|
synth.renderBlock(buffer);
|
||||||
|
REQUIRE( numPlayingVoices(synth) == 1 );
|
||||||
|
synth.noteOn(10, 62, 85);
|
||||||
|
synth.renderBlock(buffer);
|
||||||
|
REQUIRE( numPlayingVoices(synth) == 1 );
|
||||||
|
auto playingVoices = getPlayingVoices(synth);
|
||||||
|
REQUIRE( playingVoices.front()->getRegion()->keyRange.containsWithEnd(62) );
|
||||||
|
synth.noteOn(10, 60, 85);
|
||||||
|
synth.renderBlock(buffer);
|
||||||
|
playingVoices = getPlayingVoices(synth);
|
||||||
|
REQUIRE( playingVoices.front()->getRegion()->keyRange.containsWithEnd(60) );
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("[Synth] Off by same group")
|
TEST_CASE("[Synth] Off by same group")
|
||||||
{
|
{
|
||||||
sfz::Synth synth;
|
sfz::Synth synth;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue