commit
a92dc773c5
22 changed files with 181 additions and 75 deletions
|
|
@ -38,30 +38,54 @@ Float ADSREnvelope::secondsToExpRate(Float timeInSeconds) const noexcept
|
||||||
return std::exp(Float(-9.0) / (timeInSeconds * sampleRate));
|
return std::exp(Float(-9.0) / (timeInSeconds * sampleRate));
|
||||||
};
|
};
|
||||||
|
|
||||||
void ADSREnvelope::reset(const EGDescription& desc, const Region& region, const MidiState& state, int delay, float velocity, float sampleRate) noexcept
|
void ADSREnvelope::reset(const EGDescription& desc, const Region& region, int delay, float velocity, float sampleRate) noexcept
|
||||||
{
|
{
|
||||||
this->sampleRate = sampleRate;
|
this->sampleRate = sampleRate;
|
||||||
|
desc_ = &desc;
|
||||||
this->delay = delay + secondsToSamples(desc.getDelay(state, velocity));
|
triggerVelocity_ = velocity;
|
||||||
this->attackStep = secondsToLinRate(desc.getAttack(state, velocity));
|
currentState = State::Delay; // Has to be before the update
|
||||||
this->decayRate = secondsToExpRate(desc.getDecay(state, velocity));
|
updateValues(delay);
|
||||||
this->releaseRate = secondsToExpRate(desc.getRelease(state, velocity));
|
|
||||||
this->hold = secondsToSamples(desc.getHold(state, velocity));
|
|
||||||
this->sustain = clamp(desc.getSustain(state, velocity), 0.0f, 1.0f);
|
|
||||||
this->start = clamp(desc.getStart(state, velocity), 0.0f, 1.0f);
|
|
||||||
|
|
||||||
releaseDelay = 0;
|
releaseDelay = 0;
|
||||||
sustainThreshold = this->sustain + config::virtuallyZero;
|
|
||||||
shouldRelease = false;
|
shouldRelease = false;
|
||||||
freeRunning = (
|
freeRunning = (
|
||||||
(this->sustain <= Float(config::sustainFreeRunningThreshold))
|
(this->sustain <= Float(config::sustainFreeRunningThreshold))
|
||||||
|| (region.loopMode == LoopMode::one_shot && region.isOscillator())
|
|| (region.loopMode == LoopMode::one_shot && region.isOscillator())
|
||||||
);
|
);
|
||||||
currentValue = this->start;
|
currentValue = this->start;
|
||||||
currentState = State::Delay;
|
}
|
||||||
|
|
||||||
|
void ADSREnvelope::updateValues(int delay) noexcept
|
||||||
|
{
|
||||||
|
if (currentState == State::Delay)
|
||||||
|
this->delay = delay + secondsToSamples(desc_->getDelay(midiState_, triggerVelocity_, delay));
|
||||||
|
|
||||||
|
this->attackStep = secondsToLinRate(desc_->getAttack(midiState_, triggerVelocity_, delay));
|
||||||
|
this->decayRate = secondsToExpRate(desc_->getDecay(midiState_, triggerVelocity_, delay));
|
||||||
|
this->releaseRate = secondsToExpRate(desc_->getRelease(midiState_, triggerVelocity_, delay));
|
||||||
|
this->hold = secondsToSamples(desc_->getHold(midiState_, triggerVelocity_, delay));
|
||||||
|
this->sustain = clamp(desc_->getSustain(midiState_, triggerVelocity_, delay), 0.0f, 1.0f);
|
||||||
|
this->start = clamp(desc_->getStart(midiState_, triggerVelocity_, delay), 0.0f, 1.0f);
|
||||||
|
sustainThreshold = this->sustain + config::virtuallyZero;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ADSREnvelope::getBlock(absl::Span<Float> output) noexcept
|
void ADSREnvelope::getBlock(absl::Span<Float> output) noexcept
|
||||||
|
{
|
||||||
|
if (desc_ && desc_->dynamic) {
|
||||||
|
int processed = 0;
|
||||||
|
int remaining = static_cast<int>(output.size());
|
||||||
|
while(remaining > 0) {
|
||||||
|
updateValues(processed);
|
||||||
|
int chunkSize = min(config::processChunkSize, remaining);
|
||||||
|
getBlockInternal(output.subspan(processed, chunkSize));
|
||||||
|
processed += chunkSize;
|
||||||
|
remaining -= chunkSize;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
getBlockInternal(output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ADSREnvelope::getBlockInternal(absl::Span<Float> output) noexcept
|
||||||
{
|
{
|
||||||
State currentState = this->currentState;
|
State currentState = this->currentState;
|
||||||
Float currentValue = this->currentValue;
|
Float currentValue = this->currentValue;
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,8 @@ class ADSREnvelope {
|
||||||
public:
|
public:
|
||||||
using Float = float;
|
using Float = float;
|
||||||
|
|
||||||
ADSREnvelope() = default;
|
ADSREnvelope(const MidiState& state)
|
||||||
|
: midiState_(state) {}
|
||||||
/**
|
/**
|
||||||
* @brief Resets the ADSR envelope given a Region, the current midi state, and a delay and
|
* @brief Resets the ADSR envelope given a Region, the current midi state, and a delay and
|
||||||
* trigger velocity
|
* trigger velocity
|
||||||
|
|
@ -29,10 +30,9 @@ public:
|
||||||
* @param delay
|
* @param delay
|
||||||
* @param velocity
|
* @param velocity
|
||||||
*/
|
*/
|
||||||
void reset(const EGDescription& desc, const Region& region, const MidiState& state, int delay, float velocity, float sampleRate) noexcept;
|
void reset(const EGDescription& desc, const Region& region, int delay, float velocity, float sampleRate) noexcept;
|
||||||
/**
|
/**
|
||||||
* @brief Get a block of values for the envelope. This method tries hard to be efficient
|
* @brief Get the next block of values for the envelope.
|
||||||
* and hopefully it is.
|
|
||||||
*
|
*
|
||||||
* @param output
|
* @param output
|
||||||
*/
|
*/
|
||||||
|
|
@ -81,6 +81,8 @@ private:
|
||||||
int secondsToSamples(Float timeInSeconds) const noexcept;
|
int secondsToSamples(Float timeInSeconds) const noexcept;
|
||||||
Float secondsToLinRate(Float timeInSeconds) const noexcept;
|
Float secondsToLinRate(Float timeInSeconds) const noexcept;
|
||||||
Float secondsToExpRate(Float timeInSeconds) const noexcept;
|
Float secondsToExpRate(Float timeInSeconds) const noexcept;
|
||||||
|
void updateValues(int delay = 0) noexcept;
|
||||||
|
void getBlockInternal(absl::Span<Float> output) noexcept;
|
||||||
|
|
||||||
enum class State {
|
enum class State {
|
||||||
Delay,
|
Delay,
|
||||||
|
|
@ -94,6 +96,9 @@ private:
|
||||||
};
|
};
|
||||||
State currentState { State::Done };
|
State currentState { State::Done };
|
||||||
Float currentValue { 0.0 };
|
Float currentValue { 0.0 };
|
||||||
|
const EGDescription* desc_ { nullptr };
|
||||||
|
const MidiState& midiState_;
|
||||||
|
float triggerVelocity_ { 0.0f };
|
||||||
int delay { 0 };
|
int delay { 0 };
|
||||||
Float attackStep { 0 };
|
Float attackStep { 0 };
|
||||||
Float decayRate { 0 };
|
Float decayRate { 0 };
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,8 @@ namespace config {
|
||||||
constexpr float powerFollowerReleaseTime { 200e-3f };
|
constexpr float powerFollowerReleaseTime { 200e-3f };
|
||||||
constexpr uint16_t numCCs { 512 };
|
constexpr uint16_t numCCs { 512 };
|
||||||
constexpr int maxCurves { 256 };
|
constexpr int maxCurves { 256 };
|
||||||
constexpr int chunkSize { 1024 };
|
constexpr int fileChunkSize { 1024 };
|
||||||
|
constexpr int processChunkSize { 16 };
|
||||||
constexpr unsigned int defaultAlignment { 16 };
|
constexpr unsigned int defaultAlignment { 16 };
|
||||||
constexpr int filtersInPool { maxVoices * 2 };
|
constexpr int filtersInPool { maxVoices * 2 };
|
||||||
constexpr int excessFileFrames { 64 };
|
constexpr int excessFileFrames { 64 };
|
||||||
|
|
|
||||||
|
|
@ -151,6 +151,7 @@ FloatSpec egPercent { 0.0f, {0.0f, 100.0f}, kNormalizePercent|kPermissiveBounds
|
||||||
FloatSpec egPercentMod { 0.0f, {-100.0f, 100.0f}, kNormalizePercent|kPermissiveBounds };
|
FloatSpec egPercentMod { 0.0f, {-100.0f, 100.0f}, kNormalizePercent|kPermissiveBounds };
|
||||||
FloatSpec egDepth { 0.0f, {-12000.0f, 12000.0f}, kPermissiveBounds };
|
FloatSpec egDepth { 0.0f, {-12000.0f, 12000.0f}, kPermissiveBounds };
|
||||||
FloatSpec egVel2Depth { 0.0f, {-12000.0f, 12000.0f}, kPermissiveBounds };
|
FloatSpec egVel2Depth { 0.0f, {-12000.0f, 12000.0f}, kPermissiveBounds };
|
||||||
|
BoolSpec egDynamic { 0, {0, 1}, kEnforceBounds };
|
||||||
BoolSpec flexEGAmpeg { false, {0, 1}, kEnforceBounds };
|
BoolSpec flexEGAmpeg { false, {0, 1}, kEnforceBounds };
|
||||||
BoolSpec flexEGDynamic { 0, {0, 1}, kEnforceBounds };
|
BoolSpec flexEGDynamic { 0, {0, 1}, kEnforceBounds };
|
||||||
Int32Spec flexEGSustain { 0, {0, 100}, kEnforceLowerBound|kPermissiveUpperBound };
|
Int32Spec flexEGSustain { 0, {0, 100}, kEnforceLowerBound|kPermissiveUpperBound };
|
||||||
|
|
|
||||||
|
|
@ -263,6 +263,7 @@ namespace Default
|
||||||
extern const OpcodeSpec<float> egPercentMod;
|
extern const OpcodeSpec<float> egPercentMod;
|
||||||
extern const OpcodeSpec<float> egDepth;
|
extern const OpcodeSpec<float> egDepth;
|
||||||
extern const OpcodeSpec<float> egVel2Depth;
|
extern const OpcodeSpec<float> egVel2Depth;
|
||||||
|
extern const OpcodeSpec<bool> egDynamic;
|
||||||
extern const OpcodeSpec<bool> flexEGAmpeg;
|
extern const OpcodeSpec<bool> flexEGAmpeg;
|
||||||
extern const OpcodeSpec<bool> flexEGDynamic;
|
extern const OpcodeSpec<bool> flexEGDynamic;
|
||||||
extern const OpcodeSpec<int32_t> flexEGSustain;
|
extern const OpcodeSpec<int32_t> flexEGSustain;
|
||||||
|
|
|
||||||
|
|
@ -42,22 +42,6 @@ namespace sfz {
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief If a cc switch exists for the value, returns the value with the CC modifier, otherwise returns the value alone.
|
|
||||||
*
|
|
||||||
* @param ccValues
|
|
||||||
* @param ccSwitch
|
|
||||||
* @param value
|
|
||||||
* @return float
|
|
||||||
*/
|
|
||||||
inline float ccSwitchedValue(const MidiState& state, const absl::optional<CCData<float>>& ccSwitch, float value) noexcept
|
|
||||||
{
|
|
||||||
if (ccSwitch)
|
|
||||||
return value + ccSwitch->data * state.getCCValue(ccSwitch->cc);
|
|
||||||
else
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct EGDescription {
|
struct EGDescription {
|
||||||
EGDescription() = default;
|
EGDescription() = default;
|
||||||
EGDescription(const EGDescription&) = default;
|
EGDescription(const EGDescription&) = default;
|
||||||
|
|
@ -89,6 +73,7 @@ struct EGDescription {
|
||||||
CCMap<float> ccRelease;
|
CCMap<float> ccRelease;
|
||||||
CCMap<float> ccStart;
|
CCMap<float> ccStart;
|
||||||
CCMap<float> ccSustain;
|
CCMap<float> ccSustain;
|
||||||
|
bool dynamic { false };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get the attack with possibly a CC modifier and a velocity modifier
|
* @brief Get the attack with possibly a CC modifier and a velocity modifier
|
||||||
|
|
@ -97,12 +82,12 @@ struct EGDescription {
|
||||||
* @param velocity
|
* @param velocity
|
||||||
* @return float
|
* @return float
|
||||||
*/
|
*/
|
||||||
float getAttack(const MidiState& state, float velocity) const noexcept
|
float getAttack(const MidiState& state, float velocity, int delay = 0) const noexcept
|
||||||
{
|
{
|
||||||
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
|
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
|
||||||
float returnedValue { attack + velocity * vel2attack };
|
float returnedValue { attack + velocity * vel2attack };
|
||||||
for (auto& mod: ccAttack) {
|
for (auto& mod: ccAttack) {
|
||||||
returnedValue += state.getCCValue(mod.cc) * mod.data;
|
returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data;
|
||||||
}
|
}
|
||||||
return returnedValue;
|
return returnedValue;
|
||||||
}
|
}
|
||||||
|
|
@ -113,12 +98,12 @@ struct EGDescription {
|
||||||
* @param velocity
|
* @param velocity
|
||||||
* @return float
|
* @return float
|
||||||
*/
|
*/
|
||||||
float getDecay(const MidiState& state, float velocity) const noexcept
|
float getDecay(const MidiState& state, float velocity, int delay = 0) const noexcept
|
||||||
{
|
{
|
||||||
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
|
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
|
||||||
float returnedValue { decay + velocity * vel2decay };
|
float returnedValue { decay + velocity * vel2decay };
|
||||||
for (auto& mod: ccDecay) {
|
for (auto& mod: ccDecay) {
|
||||||
returnedValue += state.getCCValue(mod.cc) * mod.data;
|
returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data;
|
||||||
}
|
}
|
||||||
return returnedValue;
|
return returnedValue;
|
||||||
}
|
}
|
||||||
|
|
@ -129,12 +114,12 @@ struct EGDescription {
|
||||||
* @param velocity
|
* @param velocity
|
||||||
* @return float
|
* @return float
|
||||||
*/
|
*/
|
||||||
float getDelay(const MidiState& state, float velocity) const noexcept
|
float getDelay(const MidiState& state, float velocity, int delay = 0) const noexcept
|
||||||
{
|
{
|
||||||
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
|
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
|
||||||
float returnedValue { delay + velocity * vel2delay };
|
float returnedValue { this->delay + velocity * vel2delay };
|
||||||
for (auto& mod: ccDelay) {
|
for (auto& mod: ccDelay) {
|
||||||
returnedValue += state.getCCValue(mod.cc) * mod.data;
|
returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data;
|
||||||
}
|
}
|
||||||
return returnedValue;
|
return returnedValue;
|
||||||
}
|
}
|
||||||
|
|
@ -145,12 +130,12 @@ struct EGDescription {
|
||||||
* @param velocity
|
* @param velocity
|
||||||
* @return float
|
* @return float
|
||||||
*/
|
*/
|
||||||
float getHold(const MidiState& state, float velocity) const noexcept
|
float getHold(const MidiState& state, float velocity, int delay = 0) const noexcept
|
||||||
{
|
{
|
||||||
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
|
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
|
||||||
float returnedValue { hold + velocity * vel2hold };
|
float returnedValue { hold + velocity * vel2hold };
|
||||||
for (auto& mod: ccHold) {
|
for (auto& mod: ccHold) {
|
||||||
returnedValue += state.getCCValue(mod.cc) * mod.data;
|
returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data;
|
||||||
}
|
}
|
||||||
return returnedValue;
|
return returnedValue;
|
||||||
}
|
}
|
||||||
|
|
@ -161,12 +146,12 @@ struct EGDescription {
|
||||||
* @param velocity
|
* @param velocity
|
||||||
* @return float
|
* @return float
|
||||||
*/
|
*/
|
||||||
float getRelease(const MidiState& state, float velocity) const noexcept
|
float getRelease(const MidiState& state, float velocity, int delay = 0) const noexcept
|
||||||
{
|
{
|
||||||
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
|
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
|
||||||
float returnedValue { release + velocity * vel2release };
|
float returnedValue { release + velocity * vel2release };
|
||||||
for (auto& mod: ccRelease) {
|
for (auto& mod: ccRelease) {
|
||||||
returnedValue += state.getCCValue(mod.cc) * mod.data;
|
returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data;
|
||||||
}
|
}
|
||||||
return returnedValue;
|
return returnedValue;
|
||||||
}
|
}
|
||||||
|
|
@ -177,12 +162,12 @@ struct EGDescription {
|
||||||
* @param velocity
|
* @param velocity
|
||||||
* @return float
|
* @return float
|
||||||
*/
|
*/
|
||||||
float getStart(const MidiState& state, float velocity) const noexcept
|
float getStart(const MidiState& state, float velocity, int delay = 0) const noexcept
|
||||||
{
|
{
|
||||||
UNUSED(velocity);
|
UNUSED(velocity);
|
||||||
float returnedValue { start };
|
float returnedValue { start };
|
||||||
for (auto& mod: ccStart) {
|
for (auto& mod: ccStart) {
|
||||||
returnedValue += state.getCCValue(mod.cc) * mod.data;
|
returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data;
|
||||||
}
|
}
|
||||||
return returnedValue;
|
return returnedValue;
|
||||||
}
|
}
|
||||||
|
|
@ -193,12 +178,12 @@ struct EGDescription {
|
||||||
* @param velocity
|
* @param velocity
|
||||||
* @return float
|
* @return float
|
||||||
*/
|
*/
|
||||||
float getSustain(const MidiState& state, float velocity) const noexcept
|
float getSustain(const MidiState& state, float velocity, int delay = 0) const noexcept
|
||||||
{
|
{
|
||||||
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
|
ASSERT(velocity >= 0.0f && velocity <= 1.0f);
|
||||||
float returnedValue { sustain + velocity * vel2sustain };
|
float returnedValue { sustain + velocity * vel2sustain };
|
||||||
for (auto& mod: ccSustain) {
|
for (auto& mod: ccSustain) {
|
||||||
returnedValue += state.getCCValue(mod.cc) * mod.data;
|
returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data;
|
||||||
}
|
}
|
||||||
return returnedValue;
|
return returnedValue;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@ void streamFromFile(sfz::AudioReader& reader, sfz::FileAudioBuffer& output, std:
|
||||||
{
|
{
|
||||||
const auto numFrames = static_cast<size_t>(reader.frames());
|
const auto numFrames = static_cast<size_t>(reader.frames());
|
||||||
const auto numChannels = reader.channels();
|
const auto numChannels = reader.channels();
|
||||||
const auto chunkSize = static_cast<size_t>(sfz::config::chunkSize);
|
const auto chunkSize = static_cast<size_t>(sfz::config::fileChunkSize);
|
||||||
|
|
||||||
output.reset();
|
output.reset();
|
||||||
output.addChannels(reader.channels());
|
output.addChannels(reader.channels());
|
||||||
|
|
|
||||||
|
|
@ -86,19 +86,19 @@ void FlexEGs::clearUnusedCurves()
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
float FlexEGPoint::getTime(const MidiState& state) const noexcept
|
float FlexEGPoint::getTime(const MidiState& state, int delay) const noexcept
|
||||||
{
|
{
|
||||||
float returnedValue { time };
|
float returnedValue { time };
|
||||||
for (const CCData<float>& mod : ccTime)
|
for (const CCData<float>& mod : ccTime)
|
||||||
returnedValue += state.getCCValue(mod.cc) * mod.data;
|
returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data;
|
||||||
return returnedValue;
|
return returnedValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
float FlexEGPoint::getLevel(const MidiState& state) const noexcept
|
float FlexEGPoint::getLevel(const MidiState& state, int delay) const noexcept
|
||||||
{
|
{
|
||||||
float returnedValue { level };
|
float returnedValue { level };
|
||||||
for (const CCData<float>& mod : ccLevel)
|
for (const CCData<float>& mod : ccLevel)
|
||||||
returnedValue += state.getCCValue(mod.cc) * mod.data;
|
returnedValue += state.getCCValueAt(mod.cc, delay) * mod.data;
|
||||||
return returnedValue;
|
return returnedValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,8 @@ struct FlexEGPoint {
|
||||||
CCMap<float> ccTime;
|
CCMap<float> ccTime;
|
||||||
CCMap<float> ccLevel;
|
CCMap<float> ccLevel;
|
||||||
|
|
||||||
float getTime(const MidiState& state) const noexcept;
|
float getTime(const MidiState& state, int delay = 0) const noexcept;
|
||||||
float getLevel(const MidiState& state) const noexcept;
|
float getLevel(const MidiState& state, int delay = 0) const noexcept;
|
||||||
|
|
||||||
void setShape(float shape);
|
void setShape(float shape);
|
||||||
float shape() const noexcept { return shape_; }
|
float shape() const noexcept { return shape_; }
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ struct FlexEnvelope::Impl {
|
||||||
void process(absl::Span<float> out);
|
void process(absl::Span<float> out);
|
||||||
bool advanceToStage(unsigned stageNumber);
|
bool advanceToStage(unsigned stageNumber);
|
||||||
bool advanceToNextStage();
|
bool advanceToNextStage();
|
||||||
void updateCurrentTimeAndLevel();
|
void updateCurrentTimeAndLevel(int delay = 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
FlexEnvelope::FlexEnvelope(Resources &resources)
|
FlexEnvelope::FlexEnvelope(Resources &resources)
|
||||||
|
|
@ -154,7 +154,19 @@ bool FlexEnvelope::isFinished() const noexcept
|
||||||
void FlexEnvelope::process(absl::Span<float> out)
|
void FlexEnvelope::process(absl::Span<float> out)
|
||||||
{
|
{
|
||||||
Impl& impl = *impl_;
|
Impl& impl = *impl_;
|
||||||
impl.process(out);
|
if (impl.desc_->dynamic) {
|
||||||
|
int processed = 0;
|
||||||
|
int remaining = static_cast<int>(out.size());
|
||||||
|
while(remaining > 0) {
|
||||||
|
impl.updateCurrentTimeAndLevel(processed);
|
||||||
|
int chunkSize = min(config::processChunkSize, remaining);
|
||||||
|
impl.process(out.subspan(processed, chunkSize));
|
||||||
|
processed += chunkSize;
|
||||||
|
remaining -= chunkSize;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
impl.process(out);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FlexEnvelope::Impl::process(absl::Span<float> out)
|
void FlexEnvelope::Impl::process(absl::Span<float> out)
|
||||||
|
|
@ -268,7 +280,7 @@ bool FlexEnvelope::Impl::advanceToNextStage()
|
||||||
return advanceToStage(currentStageNumber_ + 1);
|
return advanceToStage(currentStageNumber_ + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FlexEnvelope::Impl::updateCurrentTimeAndLevel()
|
void FlexEnvelope::Impl::updateCurrentTimeAndLevel(int delay)
|
||||||
{
|
{
|
||||||
const FlexEGDescription& desc = *desc_;
|
const FlexEGDescription& desc = *desc_;
|
||||||
if (currentStageNumber_ >= desc.points.size())
|
if (currentStageNumber_ >= desc.points.size())
|
||||||
|
|
@ -276,8 +288,8 @@ void FlexEnvelope::Impl::updateCurrentTimeAndLevel()
|
||||||
|
|
||||||
const FlexEGPoint& point = desc.points[currentStageNumber_];
|
const FlexEGPoint& point = desc.points[currentStageNumber_];
|
||||||
const MidiState& midiState = resources_->getMidiState();
|
const MidiState& midiState = resources_->getMidiState();
|
||||||
stageTargetLevel_ = point.getLevel(midiState);
|
stageTargetLevel_ = point.getLevel(midiState, delay);
|
||||||
stageTime_ = point.getTime(midiState);
|
stageTime_ = point.getTime(midiState, delay);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace sfz
|
} // namespace sfz
|
||||||
|
|
|
||||||
|
|
@ -202,6 +202,17 @@ float sfz::MidiState::getCCValue(int ccNumber) const noexcept
|
||||||
return ccEvents[ccNumber].back().value;
|
return ccEvents[ccNumber].back().value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float sfz::MidiState::getCCValueAt(int ccNumber, int delay) const noexcept
|
||||||
|
{
|
||||||
|
ASSERT(ccNumber >= 0 && ccNumber < config::numCCs);
|
||||||
|
const auto ccEvent = absl::c_lower_bound(
|
||||||
|
ccEvents[ccNumber], delay, MidiEventDelayComparator {});
|
||||||
|
if (ccEvent != ccEvents[ccNumber].end())
|
||||||
|
return ccEvent->value;
|
||||||
|
else
|
||||||
|
return ccEvents[ccNumber].back().value;
|
||||||
|
}
|
||||||
|
|
||||||
void sfz::MidiState::reset() noexcept
|
void sfz::MidiState::reset() noexcept
|
||||||
{
|
{
|
||||||
for (auto& velocity: lastNoteVelocities)
|
for (auto& velocity: lastNoteVelocities)
|
||||||
|
|
|
||||||
|
|
@ -167,13 +167,22 @@ public:
|
||||||
bool isNotePressed(int noteNumber) const noexcept { return noteStates[noteNumber]; }
|
bool isNotePressed(int noteNumber) const noexcept { return noteStates[noteNumber]; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get the CC value for CC number
|
* @brief Get the last CC value for CC number
|
||||||
*
|
*
|
||||||
* @param ccNumber
|
* @param ccNumber
|
||||||
* @return float
|
* @return float
|
||||||
*/
|
*/
|
||||||
float getCCValue(int ccNumber) const noexcept;
|
float getCCValue(int ccNumber) const noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the CC value for CC number
|
||||||
|
*
|
||||||
|
* @param ccNumber
|
||||||
|
* @param delay
|
||||||
|
* @return float
|
||||||
|
*/
|
||||||
|
float getCCValueAt(int ccNumber, int delay) const noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Reset the midi state (does not impact the last note on time)
|
* @brief Reset the midi state (does not impact the last note on time)
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ public:
|
||||||
* @param factor
|
* @param factor
|
||||||
* @param chunkSize
|
* @param chunkSize
|
||||||
*/
|
*/
|
||||||
Oversampler(Oversampling factor = Oversampling::x1, size_t chunkSize = config::chunkSize);
|
Oversampler(Oversampling factor = Oversampling::x1, size_t chunkSize = config::fileChunkSize);
|
||||||
/**
|
/**
|
||||||
* @brief Stream the oversampling of an input AudioBuffer into an output
|
* @brief Stream the oversampling of an input AudioBuffer into an output
|
||||||
* one, possibly signaling the caller along the way of the number of
|
* one, possibly signaling the caller along the way of the number of
|
||||||
|
|
|
||||||
|
|
@ -1143,6 +1143,10 @@ bool sfz::Region::parseEGOpcode(const Opcode& opcode, EGDescription& eg)
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case_any_eg("dynamic"):
|
||||||
|
eg.dynamic = opcode.read(Default::egDynamic);
|
||||||
|
break;
|
||||||
|
|
||||||
case hash("pitcheg_depth"):
|
case hash("pitcheg_depth"):
|
||||||
getOrCreateConnection(
|
getOrCreateConnection(
|
||||||
ModKey::createNXYZ(ModId::PitchEG, id),
|
ModKey::createNXYZ(ModId::PitchEG, id),
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ Synth::Impl::Impl()
|
||||||
genController_.reset(new ControllerSource(resources_, voiceManager_));
|
genController_.reset(new ControllerSource(resources_, voiceManager_));
|
||||||
genLFO_.reset(new LFOSource(voiceManager_));
|
genLFO_.reset(new LFOSource(voiceManager_));
|
||||||
genFlexEnvelope_.reset(new FlexEnvelopeSource(voiceManager_));
|
genFlexEnvelope_.reset(new FlexEnvelopeSource(voiceManager_));
|
||||||
genADSREnvelope_.reset(new ADSREnvelopeSource(voiceManager_, midiState));
|
genADSREnvelope_.reset(new ADSREnvelopeSource(voiceManager_));
|
||||||
genChannelAftertouch_.reset(new ChannelAftertouchSource(voiceManager_, midiState));
|
genChannelAftertouch_.reset(new ChannelAftertouchSource(voiceManager_, midiState));
|
||||||
genPolyAftertouch_.reset(new PolyAftertouchSource(voiceManager_, midiState));
|
genPolyAftertouch_.reset(new PolyAftertouchSource(voiceManager_, midiState));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1125,6 +1125,33 @@ void sfz::Synth::dispatchMessage(Client& client, int delay, const char* path, co
|
||||||
client.receive<'f'>(delay, path, region.amplitudeEG.vel2depth);
|
client.receive<'f'>(delay, path, region.amplitudeEG.vel2depth);
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
|
MATCH("/region&/ampeg_dynamic", "") {
|
||||||
|
GET_REGION_OR_BREAK(indices[0])
|
||||||
|
if (region.amplitudeEG.dynamic) {
|
||||||
|
client.receive<'T'>(delay, path, {});
|
||||||
|
} else {
|
||||||
|
client.receive<'F'>(delay, path, {});
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
MATCH("/region&/fileg_dynamic", "") {
|
||||||
|
GET_REGION_OR_BREAK(indices[0])
|
||||||
|
if (region.filterEG && region.filterEG->dynamic) {
|
||||||
|
client.receive<'T'>(delay, path, {});
|
||||||
|
} else {
|
||||||
|
client.receive<'F'>(delay, path, {});
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
MATCH("/region&/pitcheg_dynamic", "") {
|
||||||
|
GET_REGION_OR_BREAK(indices[0])
|
||||||
|
if (region.pitchEG && region.pitchEG->dynamic) {
|
||||||
|
client.receive<'T'>(delay, path, {});
|
||||||
|
} else {
|
||||||
|
client.receive<'F'>(delay, path, {});
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
MATCH("/region&/note_polyphony", "") {
|
MATCH("/region&/note_polyphony", "") {
|
||||||
GET_REGION_OR_BREAK(indices[0])
|
GET_REGION_OR_BREAK(indices[0])
|
||||||
if (region.notePolyphony) {
|
if (region.notePolyphony) {
|
||||||
|
|
|
||||||
|
|
@ -272,7 +272,7 @@ struct Voice::Impl
|
||||||
std::unique_ptr<LFO> lfoPitch_;
|
std::unique_ptr<LFO> lfoPitch_;
|
||||||
std::unique_ptr<LFO> lfoFilter_;
|
std::unique_ptr<LFO> lfoFilter_;
|
||||||
|
|
||||||
ADSREnvelope egAmplitude_;
|
ADSREnvelope egAmplitude_ { resources_.getMidiState() };
|
||||||
std::unique_ptr<ADSREnvelope> egPitch_;
|
std::unique_ptr<ADSREnvelope> egPitch_;
|
||||||
std::unique_ptr<ADSREnvelope> egFilter_;
|
std::unique_ptr<ADSREnvelope> egFilter_;
|
||||||
|
|
||||||
|
|
@ -1835,7 +1835,7 @@ void Voice::setPitchEGEnabledPerVoice(bool havePitchEG)
|
||||||
{
|
{
|
||||||
Impl& impl = *impl_;
|
Impl& impl = *impl_;
|
||||||
if (havePitchEG)
|
if (havePitchEG)
|
||||||
impl.egPitch_.reset(new ADSREnvelope);
|
impl.egPitch_.reset(new ADSREnvelope(impl.resources_.getMidiState()));
|
||||||
else
|
else
|
||||||
impl.egPitch_.reset();
|
impl.egPitch_.reset();
|
||||||
}
|
}
|
||||||
|
|
@ -1844,7 +1844,7 @@ void Voice::setFilterEGEnabledPerVoice(bool haveFilterEG)
|
||||||
{
|
{
|
||||||
Impl& impl = *impl_;
|
Impl& impl = *impl_;
|
||||||
if (haveFilterEG)
|
if (haveFilterEG)
|
||||||
impl.egFilter_.reset(new ADSREnvelope);
|
impl.egFilter_.reset(new ADSREnvelope(impl.resources_.getMidiState()));
|
||||||
else
|
else
|
||||||
impl.egFilter_.reset();
|
impl.egFilter_.reset();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,8 @@
|
||||||
|
|
||||||
namespace sfz {
|
namespace sfz {
|
||||||
|
|
||||||
ADSREnvelopeSource::ADSREnvelopeSource(VoiceManager& manager, MidiState& state)
|
ADSREnvelopeSource::ADSREnvelopeSource(VoiceManager& manager)
|
||||||
: voiceManager_(manager), midiState_(state)
|
: voiceManager_(manager)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,7 +79,7 @@ void ADSREnvelopeSource::init(const ModKey& sourceKey, NumericId<Voice> voiceId,
|
||||||
|
|
||||||
const TriggerEvent& triggerEvent = voice->getTriggerEvent();
|
const TriggerEvent& triggerEvent = voice->getTriggerEvent();
|
||||||
const float sampleRate = voice->getSampleRate();
|
const float sampleRate = voice->getSampleRate();
|
||||||
eg->reset(*desc, *region, midiState_, delay, triggerEvent.value, sampleRate);
|
eg->reset(*desc, *region, delay, triggerEvent.value, sampleRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ADSREnvelopeSource::release(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay)
|
void ADSREnvelopeSource::release(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay)
|
||||||
|
|
|
||||||
|
|
@ -7,14 +7,13 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "../ModGenerator.h"
|
#include "../ModGenerator.h"
|
||||||
#include "../../VoiceManager.h"
|
#include "../../VoiceManager.h"
|
||||||
#include "../../MidiState.h"
|
|
||||||
|
|
||||||
namespace sfz {
|
namespace sfz {
|
||||||
class Synth;
|
class Synth;
|
||||||
|
|
||||||
class ADSREnvelopeSource : public ModGenerator {
|
class ADSREnvelopeSource : public ModGenerator {
|
||||||
public:
|
public:
|
||||||
explicit ADSREnvelopeSource(VoiceManager &manager, MidiState& state);
|
explicit ADSREnvelopeSource(VoiceManager &manager);
|
||||||
void init(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override;
|
void init(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override;
|
||||||
void release(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override;
|
void release(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override;
|
||||||
void cancelRelease(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override;
|
void cancelRelease(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override;
|
||||||
|
|
@ -22,7 +21,6 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
VoiceManager& voiceManager_;
|
VoiceManager& voiceManager_;
|
||||||
MidiState& midiState_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace sfz
|
} // namespace sfz
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ TEST_CASE("[EGDescription] Delay range")
|
||||||
//REQUIRE(eg.getDelay(state, 127_norm) == 0.0f);
|
//REQUIRE(eg.getDelay(state, 127_norm) == 0.0f);
|
||||||
state.ccEvent(0, 63, 127_norm);
|
state.ccEvent(0, 63, 127_norm);
|
||||||
REQUIRE(eg.getDelay(state, 127_norm) == 1.0f);
|
REQUIRE(eg.getDelay(state, 127_norm) == 1.0f);
|
||||||
REQUIRE(eg.getDelay(state, 0_norm) == 2.27f);
|
REQUIRE(eg.getDelay(state, 0_norm, 1) == 2.27f);
|
||||||
//eg.ccDelay[63] = 127.0f;
|
//eg.ccDelay[63] = 127.0f;
|
||||||
//REQUIRE(eg.getDelay(state, 0_norm) == 100.0f);
|
//REQUIRE(eg.getDelay(state, 0_norm) == 100.0f);
|
||||||
eg.ccDelay[63] = 1.27f;
|
eg.ccDelay[63] = 1.27f;
|
||||||
|
|
|
||||||
|
|
@ -222,8 +222,8 @@ TEST_CASE("[Polyphony] Self-masking")
|
||||||
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"(
|
synth.loadSfzString(fs::current_path() / "tests/TestFiles/polyphony.sfz", R"(
|
||||||
<region> sample=*sine key=64 note_polyphony=2
|
<region> sample=*sine key=64 note_polyphony=2
|
||||||
)");
|
)");
|
||||||
synth.noteOn(0, 64, 63 );
|
synth.noteOn(0, 64, 63);
|
||||||
synth.noteOn(1, 64, 62 );
|
synth.noteOn(1, 64, 62);
|
||||||
synth.noteOn(2, 64, 64);
|
synth.noteOn(2, 64, 64);
|
||||||
synth.renderBlock(buffer);
|
synth.renderBlock(buffer);
|
||||||
REQUIRE( synth.getNumActiveVoices() == 3 ); // One of these is releasing
|
REQUIRE( synth.getNumActiveVoices() == 3 ); // One of these is releasing
|
||||||
|
|
|
||||||
|
|
@ -3374,3 +3374,31 @@ TEST_CASE("[Values] Flex EGs CC")
|
||||||
};
|
};
|
||||||
REQUIRE(messageList == expected);
|
REQUIRE(messageList == expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[Values] Dynamic EGs")
|
||||||
|
{
|
||||||
|
Synth synth;
|
||||||
|
std::vector<std::string> messageList;
|
||||||
|
Client client(&messageList);
|
||||||
|
client.setReceiveCallback(&simpleMessageReceiver);
|
||||||
|
|
||||||
|
synth.loadSfzString(fs::current_path() / "tests/TestFiles/value_tests.sfz", R"(
|
||||||
|
<region> sample=kick.wav
|
||||||
|
<region> sample=kick.wav ampeg_dynamic=1 pitcheg_dynamic=1 fileg_dynamic=1
|
||||||
|
)");
|
||||||
|
synth.dispatchMessage(client, 0, "/region0/ampeg_dynamic", "", nullptr);
|
||||||
|
synth.dispatchMessage(client, 0, "/region0/pitcheg_dynamic", "", nullptr);
|
||||||
|
synth.dispatchMessage(client, 0, "/region0/fileg_dynamic", "", nullptr);
|
||||||
|
synth.dispatchMessage(client, 0, "/region1/ampeg_dynamic", "", nullptr);
|
||||||
|
synth.dispatchMessage(client, 0, "/region1/pitcheg_dynamic", "", nullptr);
|
||||||
|
synth.dispatchMessage(client, 0, "/region1/fileg_dynamic", "", nullptr);
|
||||||
|
std::vector<std::string> expected {
|
||||||
|
"/region0/ampeg_dynamic,F : { }",
|
||||||
|
"/region0/pitcheg_dynamic,F : { }",
|
||||||
|
"/region0/fileg_dynamic,F : { }",
|
||||||
|
"/region1/ampeg_dynamic,T : { }",
|
||||||
|
"/region1/pitcheg_dynamic,T : { }",
|
||||||
|
"/region1/fileg_dynamic,T : { }",
|
||||||
|
};
|
||||||
|
REQUIRE(messageList == expected);
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue