// 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 #include #include 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 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 range, CCMap *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 id; // Sound source: sample playback FileId sampleId {}; // Sample absl::optional 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 offsetCC { Default::offset }; uint32_t sampleEnd { Default::sampleEndRange.getEnd() }; // end absl::optional sampleCount {}; // count absl::optional loopMode {}; // loopmode Range 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 offBy {}; // off_by SfzOffMode offMode { Default::offMode }; // off_mode absl::optional notePolyphony {}; SfzSelfMask selfMask { Default::selfMask }; // Region logic: key mapping Range keyRange { Default::keyRange }; //lokey, hikey and key Range velocityRange { Default::velocityRange }; // hivel and lovel // Region logic: MIDI conditions Range bendRange { Default::bendValueRange }; // hibend and lobend CCMap> ccConditions { Default::ccValueRange }; Range keyswitchRange { Default::keyRange }; // sw_hikey and sw_lokey absl::optional keyswitch {}; // sw_last absl::optional keyswitchLabel {}; absl::optional keyswitchUp {}; // sw_up absl::optional keyswitchDown {}; // sw_down absl::optional 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 aftertouchRange { Default::aftertouchRange }; // hichanaft and lochanaft Range bpmRange { Default::bpmRange }; // hibpm and lobpm Range 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> 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> velocityPoints; // amp_velcurve_N absl::optional velCurve {}; float ampRandom { Default::ampRandom }; // amp_random Range crossfadeKeyInRange { Default::crossfadeKeyInRange }; Range crossfadeKeyOutRange { Default::crossfadeKeyOutRange }; Range crossfadeVelInRange { Default::crossfadeVelInRange }; Range crossfadeVelOutRange { Default::crossfadeVelOutRange }; SfzCrossfadeCurve crossfadeKeyCurve { Default::crossfadeKeyCurve }; SfzCrossfadeCurve crossfadeVelCurve { Default::crossfadeVelCurve }; SfzCrossfadeCurve crossfadeCCCurve { Default::crossfadeCCCurve }; CCMap> crossfadeCCInRange { Default::crossfadeCCInRange }; // xfin_loccN xfin_hiccN CCMap> crossfadeCCOutRange { Default::crossfadeCCOutRange }; // xfout_loccN xfout_hiccN float rtDecay { Default::rtDecay }; // rt_decay // Filters and EQs std::vector equalizers; std::vector 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 gainToEffect; // Modifiers ModifierArray> 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 ccSwitched; bool triggerOnCC { false }; absl::string_view defaultPath { "" }; int sequenceCounter { 0 }; LEAK_DETECTOR(Region); }; } // namespace sfz