383 lines
14 KiB
C++
383 lines
14 KiB
C++
// SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
// This code is part of the sfizz library and is licensed under a BSD 2-clause
|
|
// license. You should have receive a LICENSE.md file along with the code.
|
|
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
|
|
|
|
#pragma once
|
|
#include "CCMap.h"
|
|
#include "Curve.h"
|
|
#include "LeakDetector.h"
|
|
#include "Defaults.h"
|
|
#include "EGDescription.h"
|
|
#include "EQDescription.h"
|
|
#include "FilterDescription.h"
|
|
#include "Opcode.h"
|
|
#include "AudioBuffer.h"
|
|
#include "MidiState.h"
|
|
#include "FileId.h"
|
|
#include "NumericId.h"
|
|
#include "Modifiers.h"
|
|
#include "absl/types/optional.h"
|
|
#include <bitset>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace sfz {
|
|
/**
|
|
* @brief Regions are the basic building blocks for the SFZ parsing and handling code.
|
|
* All SFZ files are made of regions that are activated when a key is pressed or a CC
|
|
* is triggered. Most opcodes constrain the situations in which a region can be activated.
|
|
* Once activated, the Synth object will find a voice to play the region.
|
|
*
|
|
* This class is mostly open as there are a ton of parameters needed for the voice to be
|
|
* able to play the region, and using getters would incur a ton of boilerplate code. Note
|
|
* also that some parameters may be parsed and stored in regions but no playing logi is
|
|
* available in the voices to take advantage of them.
|
|
*
|
|
*/
|
|
struct Region {
|
|
Region(int regionNumber, const MidiState& midiState, absl::string_view defaultPath = "")
|
|
: id{regionNumber}, midiState(midiState), defaultPath(std::move(defaultPath))
|
|
{
|
|
ccSwitched.set();
|
|
|
|
gainToEffect.reserve(5); // sufficient room for main and fx1-4
|
|
gainToEffect.push_back(1.0); // contribute 100% into the main bus
|
|
}
|
|
Region(const Region&) = default;
|
|
~Region() = default;
|
|
|
|
/**
|
|
* @brief Get the number which identifies this region
|
|
*/
|
|
NumericId<Region> getId() const noexcept
|
|
{
|
|
return id;
|
|
}
|
|
|
|
/**
|
|
* @brief Triggers on release?
|
|
*
|
|
* @return true
|
|
* @return false
|
|
*/
|
|
bool isRelease() const noexcept { return trigger == SfzTrigger::release || trigger == SfzTrigger::release_key; }
|
|
/**
|
|
* @brief Is a generator (*sine or *silence mostly)?
|
|
*
|
|
* @return true
|
|
* @return false
|
|
*/
|
|
bool isGenerator() const noexcept { return sampleId.filename().size() > 0 ? sampleId.filename()[0] == '*' : false; }
|
|
/**
|
|
* @brief Is stereo (has stereo sample or is unison oscillator)?
|
|
*
|
|
* @return true
|
|
* @return false
|
|
*/
|
|
bool isStereo() const noexcept { return hasStereoSample || ((oscillator || isGenerator()) && oscillatorMulti >= 3); }
|
|
/**
|
|
* @brief Is a looping region (at least potentially)?
|
|
*
|
|
* @return true
|
|
* @return false
|
|
*/
|
|
bool shouldLoop() const noexcept { return (loopMode == SfzLoopMode::loop_continuous || loopMode == SfzLoopMode::loop_sustain); }
|
|
/**
|
|
* @brief Given the current midi state, is the region switched on?
|
|
*
|
|
* @return true
|
|
* @return false
|
|
*/
|
|
bool isSwitchedOn() const noexcept;
|
|
/**
|
|
* @brief Register a new note on event. The region may be switched on or off using keys so
|
|
* this function updates the keyswitches state.
|
|
*
|
|
* @param noteNumber
|
|
* @param velocity
|
|
* @param randValue a random value between 0 and 1 used to randomize a bit the region activations
|
|
* and vary the samples
|
|
* @return true if the region should trigger on this event.
|
|
* @return false
|
|
*/
|
|
bool registerNoteOn(int noteNumber, float velocity, float randValue) noexcept;
|
|
/**
|
|
* @brief Register a new note off event. The region may be switched on or off using keys so
|
|
* this function updates the keyswitches state.
|
|
*
|
|
* @param noteNumber
|
|
* @param velocity
|
|
* @param randValue a random value between 0 and 1 used to randomize a bit the region activations
|
|
* and vary the samples
|
|
* @return true if the region should trigger on this event.
|
|
* @return false
|
|
*/
|
|
bool registerNoteOff(int noteNumber, float velocity, float randValue) noexcept;
|
|
/**
|
|
* @brief Register a new CC event. The region may be switched on or off using CCs so
|
|
* this function checks if it indeeds need to activate or not.
|
|
*
|
|
* @param ccNumber
|
|
* @param ccValue
|
|
* @return true if the region should trigger on this event
|
|
* @return false
|
|
*/
|
|
bool registerCC(int ccNumber, float ccValue) noexcept;
|
|
/**
|
|
* @brief Register a new pitch wheel event.
|
|
*
|
|
* @param pitch
|
|
*/
|
|
void registerPitchWheel(float pitch) noexcept;
|
|
/**
|
|
* @brief Register a new aftertouch event.
|
|
*
|
|
* @param aftertouch
|
|
*/
|
|
void registerAftertouch(uint8_t aftertouch) noexcept;
|
|
/**
|
|
* @brief Register tempo
|
|
*
|
|
* @param secondsPerQuarter
|
|
*/
|
|
void registerTempo(float secondsPerQuarter) noexcept;
|
|
|
|
/**
|
|
* @brief Get the base pitch of the region depending on which note has been
|
|
* pressed and at which velocity.
|
|
*
|
|
* @param noteNumber
|
|
* @param velocity
|
|
* @return float
|
|
*/
|
|
float getBasePitchVariation(float noteNumber, float velocity) const noexcept;
|
|
/**
|
|
* @brief Get the note-related gain of the region depending on which note has been
|
|
* pressed and at which velocity.
|
|
*
|
|
* @param noteNumber
|
|
* @param velocity
|
|
* @return float
|
|
*/
|
|
float getNoteGain(int noteNumber, float velocity) const noexcept;
|
|
/**
|
|
* @brief Get the additional crossfade gain of the region depending on the
|
|
* CC values
|
|
*
|
|
* @param ccState
|
|
* @return float
|
|
*/
|
|
float getCrossfadeGain() const noexcept;
|
|
/**
|
|
* @brief Get the base volume of the region depending on which note has been
|
|
* pressed to trigger the region.
|
|
*
|
|
* @param noteNumber
|
|
* @return float
|
|
*/
|
|
float getBaseVolumedB(int noteNumber) const noexcept;
|
|
/**
|
|
* @brief Get the base gain of the region.
|
|
*
|
|
* @return float
|
|
*/
|
|
float getBaseGain() const noexcept;
|
|
/**
|
|
* @brief Get the base gain of the region.
|
|
*
|
|
* @return float
|
|
*/
|
|
float getPhase() const noexcept;
|
|
/**
|
|
* @brief Computes the gain value related to the velocity of the note
|
|
*
|
|
* @return float
|
|
*/
|
|
float velocityCurve(float velocity) const noexcept;
|
|
/**
|
|
* @brief Get the detuning in cents for a given bend value between -1 and 1
|
|
*
|
|
* @param bend
|
|
* @return float
|
|
*/
|
|
float getBendInCents(float bend) const noexcept;
|
|
|
|
/**
|
|
* @brief Get the region offset in samples
|
|
*
|
|
* @return uint32_t
|
|
*/
|
|
uint64_t getOffset(Oversampling factor = Oversampling::x1) const noexcept;
|
|
/**
|
|
* @brief Get the region delay in seconds
|
|
*
|
|
* @return float
|
|
*/
|
|
float getDelay() const noexcept;
|
|
/**
|
|
* @brief Get the index of the sample end, either natural end or forced
|
|
* loop.
|
|
*
|
|
* @return uint32_t
|
|
*/
|
|
uint32_t trueSampleEnd(Oversampling factor = Oversampling::x1) const noexcept;
|
|
/**
|
|
* @brief Parse a new opcode into the region to fill in the proper parameters.
|
|
* This must be called multiple times for each opcode applying to this region.
|
|
*
|
|
* @param opcode
|
|
* @return true if the opcode was properly read and stored.
|
|
* @return false
|
|
*/
|
|
bool parseOpcode(const Opcode& opcode);
|
|
/**
|
|
* @brief Process a generic CC opcode, and fill the modulation parameters.
|
|
*
|
|
* @param opcode
|
|
* @param range
|
|
* @param ccMap
|
|
* @return true if the opcode was properly read and stored.
|
|
* @return false
|
|
*/
|
|
bool processGenericCc(const Opcode& opcode, Range<float> range, CCMap<Modifier> *ccMap);
|
|
|
|
void offsetAllKeys(int offset) noexcept;
|
|
|
|
uint32_t loopStart(Oversampling factor = Oversampling::x1) const noexcept;
|
|
uint32_t loopEnd(Oversampling factor = Oversampling::x1) const noexcept;
|
|
|
|
bool hasKeyswitches() const noexcept { return keyswitchDown || keyswitchUp || keyswitch || previousNote; }
|
|
|
|
/**
|
|
* @brief Get the gain this region contributes into the input of the Nth
|
|
* effect bus
|
|
*/
|
|
float getGainToEffectBus(unsigned number) const noexcept;
|
|
|
|
const NumericId<Region> id;
|
|
|
|
// Sound source: sample playback
|
|
FileId sampleId {}; // Sample
|
|
absl::optional<int> sampleQuality {};
|
|
float delay { Default::delay }; // delay
|
|
float delayRandom { Default::delayRandom }; // delay_random
|
|
int64_t offset { Default::offset }; // offset
|
|
int64_t offsetRandom { Default::offsetRandom }; // offset_random
|
|
CCMap<int64_t> offsetCC { Default::offset };
|
|
uint32_t sampleEnd { Default::sampleEndRange.getEnd() }; // end
|
|
absl::optional<uint32_t> sampleCount {}; // count
|
|
absl::optional<SfzLoopMode> loopMode {}; // loopmode
|
|
Range<uint32_t> loopRange { Default::loopRange }; //loopstart and loopend
|
|
|
|
// Wavetable oscillator
|
|
float oscillatorPhase { Default::oscillatorPhase };
|
|
bool oscillator = false;
|
|
int oscillatorMulti = Default::oscillatorMulti;
|
|
float oscillatorDetune = Default::oscillatorDetune;
|
|
|
|
// Instrument settings: voice lifecycle
|
|
uint32_t group { Default::group }; // group
|
|
absl::optional<uint32_t> offBy {}; // off_by
|
|
SfzOffMode offMode { Default::offMode }; // off_mode
|
|
absl::optional<uint32_t> notePolyphony {};
|
|
SfzSelfMask selfMask { Default::selfMask };
|
|
|
|
// Region logic: key mapping
|
|
Range<uint8_t> keyRange { Default::keyRange }; //lokey, hikey and key
|
|
Range<float> velocityRange { Default::velocityRange }; // hivel and lovel
|
|
|
|
// Region logic: MIDI conditions
|
|
Range<float> bendRange { Default::bendValueRange }; // hibend and lobend
|
|
CCMap<Range<float>> ccConditions { Default::ccValueRange };
|
|
Range<uint8_t> keyswitchRange { Default::keyRange }; // sw_hikey and sw_lokey
|
|
absl::optional<uint8_t> keyswitch {}; // sw_last
|
|
absl::optional<std::string> keyswitchLabel {};
|
|
absl::optional<uint8_t> keyswitchUp {}; // sw_up
|
|
absl::optional<uint8_t> keyswitchDown {}; // sw_down
|
|
absl::optional<uint8_t> previousNote {}; // sw_previous
|
|
SfzVelocityOverride velocityOverride { Default::velocityOverride }; // sw_vel
|
|
bool checkSustain { Default::checkSustain }; // sustain_sw
|
|
bool checkSostenuto { Default::checkSostenuto }; // sostenuto_sw
|
|
|
|
// Region logic: internal conditions
|
|
Range<uint8_t> aftertouchRange { Default::aftertouchRange }; // hichanaft and lochanaft
|
|
Range<float> bpmRange { Default::bpmRange }; // hibpm and lobpm
|
|
Range<float> randRange { Default::randRange }; // hirand and lorand
|
|
uint8_t sequenceLength { Default::sequenceLength }; // seq_length
|
|
uint8_t sequencePosition { Default::sequencePosition }; // seq_position
|
|
|
|
// Region logic: triggers
|
|
SfzTrigger trigger { Default::trigger }; // trigger
|
|
CCMap<Range<float>> ccTriggers { Default::ccTriggerValueRange }; // on_loccN on_hiccN
|
|
|
|
// Performance parameters: amplifier
|
|
float volume { Default::volume }; // volume
|
|
float amplitude { normalizePercents(Default::amplitude) }; // amplitude
|
|
float pan { normalizePercents(Default::pan) }; // pan
|
|
float width { normalizePercents(Default::width) }; // width
|
|
float position { normalizePercents(Default::position) }; // position
|
|
uint8_t ampKeycenter { Default::ampKeycenter }; // amp_keycenter
|
|
float ampKeytrack { Default::ampKeytrack }; // amp_keytrack
|
|
float ampVeltrack { Default::ampVeltrack }; // amp_keytrack
|
|
std::vector<std::pair<uint8_t, float>> velocityPoints; // amp_velcurve_N
|
|
absl::optional<Curve> velCurve {};
|
|
float ampRandom { Default::ampRandom }; // amp_random
|
|
Range<uint8_t> crossfadeKeyInRange { Default::crossfadeKeyInRange };
|
|
Range<uint8_t> crossfadeKeyOutRange { Default::crossfadeKeyOutRange };
|
|
Range<float> crossfadeVelInRange { Default::crossfadeVelInRange };
|
|
Range<float> crossfadeVelOutRange { Default::crossfadeVelOutRange };
|
|
SfzCrossfadeCurve crossfadeKeyCurve { Default::crossfadeKeyCurve };
|
|
SfzCrossfadeCurve crossfadeVelCurve { Default::crossfadeVelCurve };
|
|
SfzCrossfadeCurve crossfadeCCCurve { Default::crossfadeCCCurve };
|
|
CCMap<Range<float>> crossfadeCCInRange { Default::crossfadeCCInRange }; // xfin_loccN xfin_hiccN
|
|
CCMap<Range<float>> crossfadeCCOutRange { Default::crossfadeCCOutRange }; // xfout_loccN xfout_hiccN
|
|
float rtDecay { Default::rtDecay }; // rt_decay
|
|
|
|
// Filters and EQs
|
|
std::vector<EQDescription> equalizers;
|
|
std::vector<FilterDescription> filters;
|
|
|
|
// Performance parameters: pitch
|
|
uint8_t pitchKeycenter { Default::pitchKeycenter }; // pitch_keycenter
|
|
int pitchKeytrack { Default::pitchKeytrack }; // pitch_keytrack
|
|
int pitchRandom { Default::pitchRandom }; // pitch_random
|
|
int pitchVeltrack { Default::pitchVeltrack }; // pitch_veltrack
|
|
int transpose { Default::transpose }; // transpose
|
|
int tune { Default::tune }; // tune
|
|
int bendUp { Default::bendUp };
|
|
int bendDown { Default::bendDown };
|
|
int bendStep { Default::bendStep };
|
|
uint8_t bendSmooth { Default::bendSmooth };
|
|
|
|
// Envelopes
|
|
EGDescription amplitudeEG;
|
|
EGDescription pitchEG;
|
|
EGDescription filterEG;
|
|
|
|
bool hasStereoSample { false };
|
|
|
|
// Effects
|
|
std::vector<float> gainToEffect;
|
|
|
|
// Modifiers
|
|
ModifierArray<CCMap<Modifier>> modifiers;
|
|
|
|
private:
|
|
const MidiState& midiState;
|
|
bool keySwitched { true };
|
|
bool previousKeySwitched { true };
|
|
bool sequenceSwitched { true };
|
|
bool pitchSwitched { true };
|
|
bool bpmSwitched { true };
|
|
bool aftertouchSwitched { true };
|
|
std::bitset<config::numCCs> ccSwitched;
|
|
bool triggerOnCC { false };
|
|
absl::string_view defaultPath { "" };
|
|
|
|
int sequenceCounter { 0 };
|
|
LEAK_DETECTOR(Region);
|
|
};
|
|
|
|
} // namespace sfz
|