Big refactor of midi state to integrate channels
This commit is contained in:
parent
f13778e279
commit
dd14d7a9ae
8 changed files with 169 additions and 87 deletions
|
|
@ -2,6 +2,7 @@
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include "SfzHelpers.h"
|
#include "SfzHelpers.h"
|
||||||
|
#include "Debug.h"
|
||||||
|
|
||||||
namespace sfz
|
namespace sfz
|
||||||
{
|
{
|
||||||
|
|
@ -11,33 +12,43 @@ namespace sfz
|
||||||
* pressed notes.
|
* pressed notes.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
struct MidiState
|
class MidiState
|
||||||
{
|
{
|
||||||
|
public:
|
||||||
/**
|
/**
|
||||||
* @brief Update the state after a note on event
|
* @brief Update the state after a note on event
|
||||||
*
|
*
|
||||||
|
* @param channel (1-based)
|
||||||
* @param noteNumber
|
* @param noteNumber
|
||||||
* @param velocity
|
* @param velocity
|
||||||
*/
|
*/
|
||||||
inline void noteOn(int noteNumber, uint8_t velocity)
|
void noteOnEvent(int channel, int noteNumber, uint8_t velocity)
|
||||||
{
|
{
|
||||||
|
ASSERT(channel >= 1 && channel <= 16);
|
||||||
|
ASSERT(noteNumber >= 0 && noteNumber <= 127);
|
||||||
|
ASSERT(velocity >= 0 && velocity <= 127);
|
||||||
|
channel = translateSfzChannelToMidi(channel);
|
||||||
if (noteNumber >= 0 && noteNumber < 128) {
|
if (noteNumber >= 0 && noteNumber < 128) {
|
||||||
lastNoteVelocities[noteNumber] = velocity;
|
lastNoteVelocities[channel][noteNumber] = velocity;
|
||||||
noteOnTimes[noteNumber] = std::chrono::steady_clock::now();
|
noteOnTimes[channel][noteNumber] = std::chrono::steady_clock::now();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Register a note off and get the note duration
|
* @brief Register a note off and get the note duration
|
||||||
*
|
*
|
||||||
|
* @param channel (1-based)
|
||||||
* @param noteNumber
|
* @param noteNumber
|
||||||
* @return float
|
* @return float
|
||||||
*/
|
*/
|
||||||
inline float getNoteDuration(int noteNumber) const
|
float getNoteDuration(int channel, int noteNumber) const
|
||||||
{
|
{
|
||||||
|
ASSERT(channel >= 1 && channel <= 16);
|
||||||
|
ASSERT(noteNumber >= 0 && noteNumber <= 127);
|
||||||
|
channel = translateSfzChannelToMidi(channel);
|
||||||
if (noteNumber >= 0 && noteNumber < 128) {
|
if (noteNumber >= 0 && noteNumber < 128) {
|
||||||
const auto noteOffTime = std::chrono::steady_clock::now();
|
const auto noteOffTime = std::chrono::steady_clock::now();
|
||||||
const auto duration = std::chrono::duration_cast<std::chrono::duration<float>>(noteOffTime - noteOnTimes[noteNumber]);
|
const auto duration = std::chrono::duration_cast<std::chrono::duration<float>>(noteOffTime - noteOnTimes[channel][noteNumber]);
|
||||||
return duration.count();
|
return duration.count();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -47,41 +58,96 @@ struct MidiState
|
||||||
/**
|
/**
|
||||||
* @brief Get the note on velocity for a given note
|
* @brief Get the note on velocity for a given note
|
||||||
*
|
*
|
||||||
|
* @param channel (1-based)
|
||||||
* @param noteNumber
|
* @param noteNumber
|
||||||
* @return uint8_t
|
* @return uint8_t
|
||||||
*/
|
*/
|
||||||
inline uint8_t getNoteVelocity(int noteNumber) const
|
uint8_t getNoteVelocity(int channel, int noteNumber) const
|
||||||
{
|
{
|
||||||
if (noteNumber >= 0 && noteNumber < 128)
|
ASSERT(channel >= 1 && channel <= 16);
|
||||||
return lastNoteVelocities[noteNumber];
|
ASSERT(noteNumber >= 0 && noteNumber <= 127);
|
||||||
|
channel = translateSfzChannelToMidi(channel);
|
||||||
return 0;
|
return lastNoteVelocities[channel][noteNumber];
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void pitchBendEvent(int pitchBendValue)
|
void pitchBendEvent(int channel, int pitchBendValue) noexcept
|
||||||
{
|
{
|
||||||
pitchBend = pitchBendValue;
|
ASSERT(channel >= 1 && channel <= 16);
|
||||||
|
ASSERT(pitchBendValue >= 8192 && pitchBendValue <= 8192);
|
||||||
|
channel = translateSfzChannelToMidi(channel);
|
||||||
|
pitchBends[channel] = pitchBendValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int getPitchBend(int channel) const noexcept
|
||||||
|
{
|
||||||
|
ASSERT(channel >= 1 && channel <= 16);
|
||||||
|
channel = translateSfzChannelToMidi(channel);
|
||||||
|
return pitchBends[channel];
|
||||||
|
}
|
||||||
|
|
||||||
|
void ccEvent(int channel, int ccNumber, uint8_t ccValue) noexcept
|
||||||
|
{
|
||||||
|
ASSERT(channel >= 1 && channel <= 16);
|
||||||
|
ASSERT(ccNumber >= 0 && ccNumber <= 127);
|
||||||
|
ASSERT(ccValue >= 0 && ccValue <= 127);
|
||||||
|
channel = translateSfzChannelToMidi(channel);
|
||||||
|
cc[channel][ccNumber] = ccValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t getCCValue(int channel, int ccNumber) const noexcept
|
||||||
|
{
|
||||||
|
ASSERT(channel >= 1 && channel <= 16);
|
||||||
|
ASSERT(ccNumber >= 0 && ccNumber <= 127);
|
||||||
|
channel = translateSfzChannelToMidi(channel);
|
||||||
|
return cc[channel][ccNumber];
|
||||||
|
}
|
||||||
|
|
||||||
|
const CCValueArray& getCCArray(int channel) const noexcept
|
||||||
|
{
|
||||||
|
ASSERT(channel >= 1 && channel <= 16);
|
||||||
|
channel = translateSfzChannelToMidi(channel);
|
||||||
|
return cc[channel];
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset() noexcept
|
||||||
|
{
|
||||||
|
for (auto& channelArray: lastNoteVelocities)
|
||||||
|
for (auto& velocity: channelArray)
|
||||||
|
velocity = 0;
|
||||||
|
|
||||||
|
for (auto& channelArray: cc)
|
||||||
|
for (auto& ccValue: channelArray)
|
||||||
|
ccValue = 0;
|
||||||
|
|
||||||
|
for (auto& bendValue: pitchBends)
|
||||||
|
bendValue = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
template<class T>
|
||||||
|
using ChannelArray = std::array<T, 16>;
|
||||||
|
template<class T>
|
||||||
|
using MidiArray = std::array<T, 128>;
|
||||||
|
using NoteOnTime = std::chrono::steady_clock::time_point;
|
||||||
/**
|
/**
|
||||||
* @brief Stores the note on times.
|
* @brief Stores the note on times.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
std::array<std::chrono::steady_clock::time_point, 128> noteOnTimes { };
|
ChannelArray<MidiArray<NoteOnTime>> noteOnTimes { };
|
||||||
/**
|
/**
|
||||||
* @brief Stores the velocity of the note ons for currently
|
* @brief Stores the velocity of the note ons for currently
|
||||||
* depressed notes.
|
* depressed notes.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
std::array<uint8_t, 128> lastNoteVelocities { };
|
ChannelArray<MidiArray<uint8_t>> lastNoteVelocities { };
|
||||||
/**
|
/**
|
||||||
* @brief Current known values for the CCs.
|
* @brief Current known values for the CCs.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
CCValueArray cc;
|
ChannelArray<CCValueArray> cc;
|
||||||
/**
|
/**
|
||||||
* Pitch bend status
|
* Pitch bend status
|
||||||
*/
|
*/
|
||||||
int pitchBend { 0 };
|
ChannelArray<int> pitchBends { 0 };
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -640,11 +640,11 @@ float sfz::Region::getBasePitchVariation(int noteNumber, uint8_t velocity) noexc
|
||||||
return centsFactor(pitchVariationInCents);
|
return centsFactor(pitchVariationInCents);
|
||||||
}
|
}
|
||||||
|
|
||||||
float sfz::Region::getBaseVolumedB(int noteNumber) noexcept
|
float sfz::Region::getBaseVolumedB(int channel, int noteNumber) noexcept
|
||||||
{
|
{
|
||||||
auto baseVolumedB = volume + volumeDistribution(Random::randomGenerator);
|
auto baseVolumedB = volume + volumeDistribution(Random::randomGenerator);
|
||||||
if (trigger == SfzTrigger::release || trigger == SfzTrigger::release_key)
|
if (trigger == SfzTrigger::release || trigger == SfzTrigger::release_key)
|
||||||
baseVolumedB -= rtDecay * midiState.getNoteDuration(noteNumber);
|
baseVolumedB -= rtDecay * midiState.getNoteDuration(channel, noteNumber);
|
||||||
return baseVolumedB;
|
return baseVolumedB;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -173,10 +173,11 @@ struct Region {
|
||||||
* @brief Get the base volume of the region depending on which note has been
|
* @brief Get the base volume of the region depending on which note has been
|
||||||
* pressed to trigger the region.
|
* pressed to trigger the region.
|
||||||
*
|
*
|
||||||
|
* @param channel (1-based)
|
||||||
* @param noteNumber
|
* @param noteNumber
|
||||||
* @return float
|
* @return float
|
||||||
*/
|
*/
|
||||||
float getBaseVolumedB(int noteNumber) noexcept;
|
float getBaseVolumedB(int channel, int noteNumber) noexcept;
|
||||||
/**
|
/**
|
||||||
* @brief Get the base gain of the region.
|
* @brief Get the base gain of the region.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -41,11 +41,22 @@ using CCNamePair = std::pair<uint8_t, std::string>;
|
||||||
* @param zeroBasedChannel
|
* @param zeroBasedChannel
|
||||||
* @return constexpr int
|
* @return constexpr int
|
||||||
*/
|
*/
|
||||||
inline constexpr int translateMidiChannelToSfz(int zeroBasedChannel)
|
constexpr int translateMidiChannelToSfz(int zeroBasedChannel)
|
||||||
{
|
{
|
||||||
return zeroBasedChannel + 1;
|
return zeroBasedChannel + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Translates the 1-based SFZ channel to the 0-based MIDI channel
|
||||||
|
*
|
||||||
|
* @param zeroBasedChannel
|
||||||
|
* @return constexpr int
|
||||||
|
*/
|
||||||
|
constexpr int translateSfzChannelToMidi(int oneBasedChannel)
|
||||||
|
{
|
||||||
|
return oneBasedChannel - 1;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Converts cents to a pitch ratio
|
* @brief Converts cents to a pitch ratio
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -145,8 +145,7 @@ void sfz::Synth::clear()
|
||||||
numCurves = 0;
|
numCurves = 0;
|
||||||
fileTicket = -1;
|
fileTicket = -1;
|
||||||
defaultSwitch = absl::nullopt;
|
defaultSwitch = absl::nullopt;
|
||||||
for (auto& state : midiState.cc)
|
midiState.reset();
|
||||||
state = 0;
|
|
||||||
ccNames.clear();
|
ccNames.clear();
|
||||||
globalOpcodes.clear();
|
globalOpcodes.clear();
|
||||||
masterOpcodes.clear();
|
masterOpcodes.clear();
|
||||||
|
|
@ -175,8 +174,10 @@ void sfz::Synth::handleControlOpcodes(const std::vector<Opcode>& members)
|
||||||
case hash("Set_cc"):
|
case hash("Set_cc"):
|
||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
case hash("set_cc"):
|
case hash("set_cc"):
|
||||||
if (member.parameter && Default::ccRange.containsWithEnd(*member.parameter))
|
if (member.parameter && Default::ccRange.containsWithEnd(*member.parameter)){
|
||||||
setValueFromOpcode(member, midiState.cc[*member.parameter], Default::ccRange);
|
for (int channel=1; channel <=16; channel++)
|
||||||
|
midiState.ccEvent(channel, *member.parameter, readOpcode(member.value, Default::ccRange).value_or(0));
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case hash("Label_cc"):
|
case hash("Label_cc"):
|
||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
|
|
@ -271,8 +272,10 @@ bool sfz::Synth::loadSfzFile(const fs::path& filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Defaults
|
// Defaults
|
||||||
for (int ccIndex = 1; ccIndex < 128; ccIndex++)
|
for (int ccIndex = 1; ccIndex < 128; ccIndex++) {
|
||||||
region->registerCC(region->channelRange.getStart(), ccIndex, midiState.cc[ccIndex]);
|
const auto channel = region->channelRange.getStart();
|
||||||
|
region->registerCC(channel, ccIndex, midiState.getCCValue(channel, ccIndex));
|
||||||
|
}
|
||||||
|
|
||||||
if (defaultSwitch) {
|
if (defaultSwitch) {
|
||||||
region->registerNoteOn(region->channelRange.getStart(), *defaultSwitch, 127, 1.0);
|
region->registerNoteOn(region->channelRange.getStart(), *defaultSwitch, 127, 1.0);
|
||||||
|
|
@ -388,7 +391,7 @@ void sfz::Synth::noteOn(int delay, int channel, int noteNumber, uint8_t velocity
|
||||||
ASSERT(noteNumber >= 0);
|
ASSERT(noteNumber >= 0);
|
||||||
|
|
||||||
channel = translateMidiChannelToSfz(channel);
|
channel = translateMidiChannelToSfz(channel);
|
||||||
midiState.noteOn(noteNumber, velocity);
|
midiState.noteOnEvent(channel, noteNumber, velocity);
|
||||||
|
|
||||||
AtomicGuard callbackGuard { inCallback };
|
AtomicGuard callbackGuard { inCallback };
|
||||||
if (!canEnterCallback)
|
if (!canEnterCallback)
|
||||||
|
|
@ -417,8 +420,6 @@ void sfz::Synth::noteOff(int delay, int channel, int noteNumber, uint8_t velocit
|
||||||
ASSERT(noteNumber < 128);
|
ASSERT(noteNumber < 128);
|
||||||
ASSERT(noteNumber >= 0);
|
ASSERT(noteNumber >= 0);
|
||||||
|
|
||||||
channel = translateMidiChannelToSfz(channel);
|
|
||||||
|
|
||||||
AtomicGuard callbackGuard { inCallback };
|
AtomicGuard callbackGuard { inCallback };
|
||||||
if (!canEnterCallback)
|
if (!canEnterCallback)
|
||||||
return;
|
return;
|
||||||
|
|
@ -426,8 +427,10 @@ void sfz::Synth::noteOff(int delay, int channel, int noteNumber, uint8_t velocit
|
||||||
// FIXME: Some keyboards (e.g. Casio PX5S) can send a real note-off velocity. In this case, do we have a
|
// FIXME: Some keyboards (e.g. Casio PX5S) can send a real note-off velocity. In this case, do we have a
|
||||||
// way in sfz to specify that a release trigger should NOT use the note-on velocity?
|
// way in sfz to specify that a release trigger should NOT use the note-on velocity?
|
||||||
// auto replacedVelocity = (velocity == 0 ? sfz::getNoteVelocity(noteNumber) : velocity);
|
// auto replacedVelocity = (velocity == 0 ? sfz::getNoteVelocity(noteNumber) : velocity);
|
||||||
auto replacedVelocity = midiState.getNoteVelocity(noteNumber);
|
auto replacedVelocity = midiState.getNoteVelocity(channel, noteNumber);
|
||||||
auto randValue = randNoteDistribution(Random::randomGenerator);
|
auto randValue = randNoteDistribution(Random::randomGenerator);
|
||||||
|
|
||||||
|
channel = translateMidiChannelToSfz(channel);
|
||||||
for (auto& voice : voices)
|
for (auto& voice : voices)
|
||||||
voice->registerNoteOff(delay, channel, noteNumber, replacedVelocity);
|
voice->registerNoteOff(delay, channel, noteNumber, replacedVelocity);
|
||||||
|
|
||||||
|
|
@ -453,10 +456,10 @@ void sfz::Synth::cc(int delay, int channel, int ccNumber, uint8_t ccValue) noexc
|
||||||
if (!canEnterCallback)
|
if (!canEnterCallback)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
midiState.ccEvent(channel, ccNumber, ccValue);
|
||||||
for (auto& voice : voices)
|
for (auto& voice : voices)
|
||||||
voice->registerCC(delay, channel, ccNumber, ccValue);
|
voice->registerCC(delay, channel, ccNumber, ccValue);
|
||||||
|
|
||||||
midiState.cc[ccNumber] = ccValue;
|
|
||||||
|
|
||||||
for (auto& region : ccActivationLists[ccNumber]) {
|
for (auto& region : ccActivationLists[ccNumber]) {
|
||||||
if (region->registerCC(channel, ccNumber, ccValue)) {
|
if (region->registerCC(channel, ccNumber, ccValue)) {
|
||||||
|
|
@ -474,7 +477,7 @@ void sfz::Synth::pitchWheel(int delay, int channel, int pitch) noexcept
|
||||||
ASSERT(pitch <= 8192);
|
ASSERT(pitch <= 8192);
|
||||||
ASSERT(pitch >= -8192);
|
ASSERT(pitch >= -8192);
|
||||||
channel = translateMidiChannelToSfz(channel);
|
channel = translateMidiChannelToSfz(channel);
|
||||||
midiState.pitchBendEvent(pitch);
|
midiState.pitchBendEvent(channel, pitch);
|
||||||
for (auto& voice: voices) {
|
for (auto& voice: voices) {
|
||||||
voice->registerPitchWheel(delay, channel, pitch);
|
voice->registerPitchWheel(delay, channel, pitch);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,39 +60,39 @@ void sfz::Voice::startVoice(Region* region, int delay, int channel, int number,
|
||||||
}
|
}
|
||||||
pitchRatio = region->getBasePitchVariation(number, value);
|
pitchRatio = region->getBasePitchVariation(number, value);
|
||||||
|
|
||||||
baseVolumedB = region->getBaseVolumedB(number);
|
baseVolumedB = region->getBaseVolumedB(channel, number);
|
||||||
|
|
||||||
auto volumedB { baseVolumedB };
|
auto volumedB { baseVolumedB };
|
||||||
if (region->volumeCC)
|
if (region->volumeCC)
|
||||||
volumedB += normalizeCC(midiState.cc[region->volumeCC->first]) * region->volumeCC->second;
|
volumedB += normalizeCC(midiState.getCCValue(channel, region->volumeCC->first)) * region->volumeCC->second;
|
||||||
volumeEnvelope.reset(db2mag(volumedB));
|
volumeEnvelope.reset(db2mag(volumedB));
|
||||||
|
|
||||||
baseGain = region->getBaseGain();
|
baseGain = region->getBaseGain();
|
||||||
baseGain *= region->getCrossfadeGain(midiState.cc);
|
baseGain *= region->getCrossfadeGain(midiState.getCCArray(channel));
|
||||||
if (triggerType != TriggerType::CC)
|
if (triggerType != TriggerType::CC)
|
||||||
baseGain *= region->getNoteGain(number, value);
|
baseGain *= region->getNoteGain(number, value);
|
||||||
|
|
||||||
float gain { baseGain };
|
float gain { baseGain };
|
||||||
if (region->amplitudeCC)
|
if (region->amplitudeCC)
|
||||||
gain *= normalizeCC(midiState.cc[region->amplitudeCC->first]) * normalizePercents(region->amplitudeCC->second);
|
gain *= normalizeCC(midiState.getCCValue(channel, region->amplitudeCC->first)) * normalizePercents(region->amplitudeCC->second);
|
||||||
amplitudeEnvelope.reset(gain);
|
amplitudeEnvelope.reset(gain);
|
||||||
|
|
||||||
basePan = normalizeNegativePercents(region->pan);
|
basePan = normalizeNegativePercents(region->pan);
|
||||||
auto pan { basePan };
|
auto pan { basePan };
|
||||||
if (region->panCC)
|
if (region->panCC)
|
||||||
pan += normalizeCC(midiState.cc[region->panCC->first]) * normalizeNegativePercents(region->panCC->second);
|
pan += normalizeCC(midiState.getCCValue(channel, region->panCC->first)) * normalizeNegativePercents(region->panCC->second);
|
||||||
panEnvelope.reset(pan);
|
panEnvelope.reset(pan);
|
||||||
|
|
||||||
basePosition = normalizeNegativePercents(region->position);
|
basePosition = normalizeNegativePercents(region->position);
|
||||||
auto position { basePosition };
|
auto position { basePosition };
|
||||||
if (region->positionCC)
|
if (region->positionCC)
|
||||||
position += normalizeCC(midiState.cc[region->positionCC->first]) * normalizeNegativePercents(region->positionCC->second);
|
position += normalizeCC(midiState.getCCValue(channel, region->positionCC->first)) * normalizeNegativePercents(region->positionCC->second);
|
||||||
positionEnvelope.reset(position);
|
positionEnvelope.reset(position);
|
||||||
|
|
||||||
baseWidth = normalizeNegativePercents(region->width);
|
baseWidth = normalizeNegativePercents(region->width);
|
||||||
auto width { baseWidth };
|
auto width { baseWidth };
|
||||||
if (region->widthCC)
|
if (region->widthCC)
|
||||||
width += normalizeCC(midiState.cc[region->widthCC->first]) * normalizeNegativePercents(region->widthCC->second);
|
width += normalizeCC(midiState.getCCValue(channel, region->widthCC->first)) * normalizeNegativePercents(region->widthCC->second);
|
||||||
widthEnvelope.reset(width);
|
widthEnvelope.reset(width);
|
||||||
|
|
||||||
pitchBendEnvelope.setFunction([region](float pitchValue){
|
pitchBendEnvelope.setFunction([region](float pitchValue){
|
||||||
|
|
@ -100,29 +100,29 @@ void sfz::Voice::startVoice(Region* region, int delay, int channel, int number,
|
||||||
const auto bendInCents = normalizedBend > 0 ? normalizedBend * region->bendUp : -normalizedBend * region->bendDown;
|
const auto bendInCents = normalizedBend > 0 ? normalizedBend * region->bendUp : -normalizedBend * region->bendDown;
|
||||||
return centsFactor(bendInCents);
|
return centsFactor(bendInCents);
|
||||||
});
|
});
|
||||||
pitchBendEnvelope.reset(midiState.pitchBend);
|
pitchBendEnvelope.reset(midiState.getPitchBend(channel));
|
||||||
|
|
||||||
sourcePosition = region->getOffset();
|
sourcePosition = region->getOffset();
|
||||||
initialDelay = delay + static_cast<uint32_t>(region->getDelay() * sampleRate);
|
initialDelay = delay + static_cast<uint32_t>(region->getDelay() * sampleRate);
|
||||||
baseFrequency = midiNoteFrequency(number);
|
baseFrequency = midiNoteFrequency(number);
|
||||||
bendStepFactor = centsFactor(region->bendStep);
|
bendStepFactor = centsFactor(region->bendStep);
|
||||||
prepareEGEnvelope(initialDelay, value);
|
prepareEGEnvelope(channel, initialDelay, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void sfz::Voice::prepareEGEnvelope(int delay, uint8_t velocity) noexcept
|
void sfz::Voice::prepareEGEnvelope(int channel, int delay, uint8_t velocity) noexcept
|
||||||
{
|
{
|
||||||
auto secondsToSamples = [this](auto timeInSeconds) {
|
auto secondsToSamples = [this](auto timeInSeconds) {
|
||||||
return static_cast<int>(timeInSeconds * sampleRate);
|
return static_cast<int>(timeInSeconds * sampleRate);
|
||||||
};
|
};
|
||||||
|
const auto& ccArray = midiState.getCCArray(channel);
|
||||||
egEnvelope.reset(
|
egEnvelope.reset(
|
||||||
secondsToSamples(region->amplitudeEG.getAttack(midiState.cc, velocity)),
|
secondsToSamples(region->amplitudeEG.getAttack(ccArray, velocity)),
|
||||||
secondsToSamples(region->amplitudeEG.getRelease(midiState.cc, velocity)),
|
secondsToSamples(region->amplitudeEG.getRelease(ccArray, velocity)),
|
||||||
normalizePercents(region->amplitudeEG.getSustain(midiState.cc, velocity)),
|
normalizePercents(region->amplitudeEG.getSustain(ccArray, velocity)),
|
||||||
delay + secondsToSamples(region->amplitudeEG.getDelay(midiState.cc, velocity)),
|
delay + secondsToSamples(region->amplitudeEG.getDelay(ccArray, velocity)),
|
||||||
secondsToSamples(region->amplitudeEG.getDecay(midiState.cc, velocity)),
|
secondsToSamples(region->amplitudeEG.getDecay(ccArray, velocity)),
|
||||||
secondsToSamples(region->amplitudeEG.getHold(midiState.cc, velocity)),
|
secondsToSamples(region->amplitudeEG.getHold(ccArray, velocity)),
|
||||||
normalizePercents(region->amplitudeEG.getStart(midiState.cc, velocity)));
|
normalizePercents(region->amplitudeEG.getStart(ccArray, velocity)));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool sfz::Voice::isFree() const noexcept
|
bool sfz::Voice::isFree() const noexcept
|
||||||
|
|
@ -152,7 +152,7 @@ void sfz::Voice::registerNoteOff(int delay, int channel, int noteNumber, uint8_t
|
||||||
if (region->loopMode == SfzLoopMode::one_shot)
|
if (region->loopMode == SfzLoopMode::one_shot)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!region->checkSustain || midiState.cc[config::sustainCC] < config::halfCCThreshold)
|
if (!region->checkSustain || midiState.getCCValue(channel, config::sustainCC) < config::halfCCThreshold)
|
||||||
release(delay);
|
release(delay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -243,10 +243,11 @@ private:
|
||||||
/**
|
/**
|
||||||
* @brief Computes the values for the envelope depending on the note or CC number and the velocity/cc value
|
* @brief Computes the values for the envelope depending on the note or CC number and the velocity/cc value
|
||||||
*
|
*
|
||||||
|
* @param channel
|
||||||
* @param delay
|
* @param delay
|
||||||
* @param velocity
|
* @param velocity
|
||||||
*/
|
*/
|
||||||
void prepareEGEnvelope(int delay, uint8_t velocity) noexcept;
|
void prepareEGEnvelope(int channel, int delay, uint8_t velocity) noexcept;
|
||||||
/**
|
/**
|
||||||
* @brief The function processing a mono sample source
|
* @brief The function processing a mono sample source
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -183,13 +183,13 @@ TEST_CASE("[Region] Crossfade in on CC")
|
||||||
region.parseOpcode({ "xfin_locc24", "20" });
|
region.parseOpcode({ "xfin_locc24", "20" });
|
||||||
region.parseOpcode({ "xfin_hicc24", "24" });
|
region.parseOpcode({ "xfin_hicc24", "24" });
|
||||||
region.parseOpcode({ "amp_veltrack", "0" });
|
region.parseOpcode({ "amp_veltrack", "0" });
|
||||||
midiState.cc[24] = 19; REQUIRE( region.getCrossfadeGain(midiState.cc) == 0.0_a );
|
midiState.ccEvent(1, 24, 19); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 0.0_a );
|
||||||
midiState.cc[24] = 20; REQUIRE( region.getCrossfadeGain(midiState.cc) == 0.0_a );
|
midiState.ccEvent(1, 24, 20); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 0.0_a );
|
||||||
midiState.cc[24] = 21; REQUIRE( region.getCrossfadeGain(midiState.cc) == 0.5_a );
|
midiState.ccEvent(1, 24, 21); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 0.5_a );
|
||||||
midiState.cc[24] = 22; REQUIRE( region.getCrossfadeGain(midiState.cc) == 0.70711_a );
|
midiState.ccEvent(1, 24, 22); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 0.70711_a );
|
||||||
midiState.cc[24] = 23; REQUIRE( region.getCrossfadeGain(midiState.cc) == 0.86603_a );
|
midiState.ccEvent(1, 24, 23); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 0.86603_a );
|
||||||
midiState.cc[24] = 24; REQUIRE( region.getCrossfadeGain(midiState.cc) == 1.0_a );
|
midiState.ccEvent(1, 24, 24); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 1.0_a );
|
||||||
midiState.cc[24] = 25; REQUIRE( region.getCrossfadeGain(midiState.cc) == 1.0_a );
|
midiState.ccEvent(1, 24, 25); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 1.0_a );
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[Region] Crossfade in on CC - gain")
|
TEST_CASE("[Region] Crossfade in on CC - gain")
|
||||||
|
|
@ -201,13 +201,13 @@ TEST_CASE("[Region] Crossfade in on CC - gain")
|
||||||
region.parseOpcode({ "xfin_hicc24", "24" });
|
region.parseOpcode({ "xfin_hicc24", "24" });
|
||||||
region.parseOpcode({ "amp_veltrack", "0" });
|
region.parseOpcode({ "amp_veltrack", "0" });
|
||||||
region.parseOpcode({ "xf_cccurve", "gain" });
|
region.parseOpcode({ "xf_cccurve", "gain" });
|
||||||
midiState.cc[24] = 19; REQUIRE( region.getCrossfadeGain(midiState.cc) == 0.0_a );
|
midiState.ccEvent(1, 24, 19); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 0.0_a );
|
||||||
midiState.cc[24] = 20; REQUIRE( region.getCrossfadeGain(midiState.cc) == 0.0_a );
|
midiState.ccEvent(1, 24, 20); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 0.0_a );
|
||||||
midiState.cc[24] = 21; REQUIRE( region.getCrossfadeGain(midiState.cc) == 0.25_a );
|
midiState.ccEvent(1, 24, 21); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 0.25_a );
|
||||||
midiState.cc[24] = 22; REQUIRE( region.getCrossfadeGain(midiState.cc) == 0.5_a );
|
midiState.ccEvent(1, 24, 22); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 0.5_a );
|
||||||
midiState.cc[24] = 23; REQUIRE( region.getCrossfadeGain(midiState.cc) == 0.75_a );
|
midiState.ccEvent(1, 24, 23); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 0.75_a );
|
||||||
midiState.cc[24] = 24; REQUIRE( region.getCrossfadeGain(midiState.cc) == 1.0_a );
|
midiState.ccEvent(1, 24, 24); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 1.0_a );
|
||||||
midiState.cc[24] = 25; REQUIRE( region.getCrossfadeGain(midiState.cc) == 1.0_a );
|
midiState.ccEvent(1, 24, 25); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 1.0_a );
|
||||||
}
|
}
|
||||||
TEST_CASE("[Region] Crossfade out on CC")
|
TEST_CASE("[Region] Crossfade out on CC")
|
||||||
{
|
{
|
||||||
|
|
@ -217,13 +217,13 @@ TEST_CASE("[Region] Crossfade out on CC")
|
||||||
region.parseOpcode({ "xfout_locc24", "20" });
|
region.parseOpcode({ "xfout_locc24", "20" });
|
||||||
region.parseOpcode({ "xfout_hicc24", "24" });
|
region.parseOpcode({ "xfout_hicc24", "24" });
|
||||||
region.parseOpcode({ "amp_veltrack", "0" });
|
region.parseOpcode({ "amp_veltrack", "0" });
|
||||||
midiState.cc[24] = 19; REQUIRE( region.getCrossfadeGain(midiState.cc) == 1.0_a );
|
midiState.ccEvent(1, 24, 19); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 1.0_a );
|
||||||
midiState.cc[24] = 20; REQUIRE( region.getCrossfadeGain(midiState.cc) == 1.0_a );
|
midiState.ccEvent(1, 24, 20); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 1.0_a );
|
||||||
midiState.cc[24] = 21; REQUIRE( region.getCrossfadeGain(midiState.cc) == 0.86603_a );
|
midiState.ccEvent(1, 24, 21); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 0.86603_a );
|
||||||
midiState.cc[24] = 22; REQUIRE( region.getCrossfadeGain(midiState.cc) == 0.70711_a );
|
midiState.ccEvent(1, 24, 22); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 0.70711_a );
|
||||||
midiState.cc[24] = 23; REQUIRE( region.getCrossfadeGain(midiState.cc) == 0.5_a );
|
midiState.ccEvent(1, 24, 23); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 0.5_a );
|
||||||
midiState.cc[24] = 24; REQUIRE( region.getCrossfadeGain(midiState.cc) == 0.0_a );
|
midiState.ccEvent(1, 24, 24); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 0.0_a );
|
||||||
midiState.cc[24] = 25; REQUIRE( region.getCrossfadeGain(midiState.cc) == 0.0_a );
|
midiState.ccEvent(1, 24, 25); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 0.0_a );
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[Region] Crossfade out on CC - gain")
|
TEST_CASE("[Region] Crossfade out on CC - gain")
|
||||||
|
|
@ -235,13 +235,13 @@ TEST_CASE("[Region] Crossfade out on CC - gain")
|
||||||
region.parseOpcode({ "xfout_hicc24", "24" });
|
region.parseOpcode({ "xfout_hicc24", "24" });
|
||||||
region.parseOpcode({ "amp_veltrack", "0" });
|
region.parseOpcode({ "amp_veltrack", "0" });
|
||||||
region.parseOpcode({ "xf_cccurve", "gain" });
|
region.parseOpcode({ "xf_cccurve", "gain" });
|
||||||
midiState.cc[24] = 19; REQUIRE( region.getCrossfadeGain(midiState.cc) == 1.0_a );
|
midiState.ccEvent(1, 24, 19); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 1.0_a );
|
||||||
midiState.cc[24] = 20; REQUIRE( region.getCrossfadeGain(midiState.cc) == 1.0_a );
|
midiState.ccEvent(1, 24, 20); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 1.0_a );
|
||||||
midiState.cc[24] = 21; REQUIRE( region.getCrossfadeGain(midiState.cc) == 0.75_a );
|
midiState.ccEvent(1, 24, 21); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 0.75_a );
|
||||||
midiState.cc[24] = 22; REQUIRE( region.getCrossfadeGain(midiState.cc) == 0.5_a );
|
midiState.ccEvent(1, 24, 22); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 0.5_a );
|
||||||
midiState.cc[24] = 23; REQUIRE( region.getCrossfadeGain(midiState.cc) == 0.25_a );
|
midiState.ccEvent(1, 24, 23); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 0.25_a );
|
||||||
midiState.cc[24] = 24; REQUIRE( region.getCrossfadeGain(midiState.cc) == 0.0_a );
|
midiState.ccEvent(1, 24, 24); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 0.0_a );
|
||||||
midiState.cc[24] = 25; REQUIRE( region.getCrossfadeGain(midiState.cc) == 0.0_a );
|
midiState.ccEvent(1, 24, 25); REQUIRE( region.getCrossfadeGain(midiState.getCCArray(1)) == 0.0_a );
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[Region] Velocity bug for extreme values - veltrack at 0")
|
TEST_CASE("[Region] Velocity bug for extreme values - veltrack at 0")
|
||||||
|
|
@ -282,17 +282,17 @@ TEST_CASE("[Region] rt_decay")
|
||||||
region.parseOpcode({ "sample", "*sine" });
|
region.parseOpcode({ "sample", "*sine" });
|
||||||
region.parseOpcode({ "trigger", "release" });
|
region.parseOpcode({ "trigger", "release" });
|
||||||
region.parseOpcode({ "rt_decay", "10" });
|
region.parseOpcode({ "rt_decay", "10" });
|
||||||
midiState.noteOn(64, 64);
|
midiState.noteOnEvent(1, 64, 64);
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
REQUIRE( region.getBaseVolumedB(64) == Approx(sfz::Default::volume - 1.0f).margin(0.1) );
|
REQUIRE( region.getBaseVolumedB(1, 64) == Approx(sfz::Default::volume - 1.0f).margin(0.1) );
|
||||||
region.parseOpcode({ "rt_decay", "20" });
|
region.parseOpcode({ "rt_decay", "20" });
|
||||||
midiState.noteOn(64, 64);
|
midiState.noteOnEvent(1, 64, 64);
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
REQUIRE( region.getBaseVolumedB(64) == Approx(sfz::Default::volume - 2.0f).margin(0.1) );
|
REQUIRE( region.getBaseVolumedB(1, 64) == Approx(sfz::Default::volume - 2.0f).margin(0.1) );
|
||||||
region.parseOpcode({ "trigger", "attack" });
|
region.parseOpcode({ "trigger", "attack" });
|
||||||
midiState.noteOn(64, 64);
|
midiState.noteOnEvent(1, 64, 64);
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
REQUIRE( region.getBaseVolumedB(64) == Approx(sfz::Default::volume).margin(0.1) );
|
REQUIRE( region.getBaseVolumedB(1, 64) == Approx(sfz::Default::volume).margin(0.1) );
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[Region] Base delay")
|
TEST_CASE("[Region] Base delay")
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue