Refactor of Synth and Voice into a pimpl pattern
This commit is contained in:
parent
b646cfc8a1
commit
3214211a06
13 changed files with 1955 additions and 1658 deletions
1555
src/sfizz/Synth.cpp
1555
src/sfizz/Synth.cpp
File diff suppressed because it is too large
Load diff
|
|
@ -5,31 +5,22 @@
|
|||
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
|
||||
|
||||
#pragma once
|
||||
#include "Resources.h"
|
||||
#include "Parser.h"
|
||||
#include "Voice.h"
|
||||
#include "Region.h"
|
||||
#include "RegionSet.h"
|
||||
#include "PolyphonyGroup.h"
|
||||
#include "Effects.h"
|
||||
#include "Voice.h"
|
||||
#include "LeakDetector.h"
|
||||
#include "MidiState.h"
|
||||
#include "AudioSpan.h"
|
||||
#include "parser/Parser.h"
|
||||
#include "VoiceStealing.h"
|
||||
#include "utility/SpinMutex.h"
|
||||
#include <absl/types/span.h>
|
||||
#include <absl/types/optional.h>
|
||||
#include <absl/strings/string_view.h>
|
||||
#include <random>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
namespace sfz {
|
||||
class ControllerSource;
|
||||
class LFOSource;
|
||||
class FlexEnvelopeSource;
|
||||
class ADSREnvelopeSource;
|
||||
|
||||
// Forward declarations for the introspection methods
|
||||
class RegionSet;
|
||||
class PolyphonyGroup;
|
||||
class EffectBus;
|
||||
|
||||
/**
|
||||
* @brief This class is the core of the sfizz library. In C++ it is the main point
|
||||
|
|
@ -68,11 +59,10 @@ class ADSREnvelopeSource;
|
|||
* The jack_client.cpp file contains examples of the most classical usage of the
|
||||
* synth and can be used as a reference.
|
||||
*/
|
||||
class Synth final : public Voice::StateListener, public Parser::Listener {
|
||||
class Synth final {
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new Synth object with no voices. If you want sound
|
||||
* you will need to call setNumVoices() before playing.
|
||||
* @brief Construct a new Synth object with a default number of voices.
|
||||
*
|
||||
*/
|
||||
Synth();
|
||||
|
|
@ -80,13 +70,10 @@ public:
|
|||
* @brief Destructor
|
||||
*/
|
||||
~Synth();
|
||||
/**
|
||||
* @brief Construct a new Synth object with a specified number of voices.
|
||||
*
|
||||
* @param numVoices
|
||||
*/
|
||||
Synth(int numVoices);
|
||||
|
||||
Synth(const Synth& other) = delete;
|
||||
Synth& operator=(const Synth& other) = delete;
|
||||
Synth(Synth&& other) = delete;
|
||||
Synth& operator=(Synth&& other) = delete;
|
||||
/**
|
||||
* @brief Processing mode
|
||||
*/
|
||||
|
|
@ -122,11 +109,6 @@ public:
|
|||
* @true otherwise.
|
||||
*/
|
||||
bool loadSfzString(const fs::path& path, absl::string_view text);
|
||||
/**
|
||||
* @brief Finalize SFZ loading, following a successful execution of the
|
||||
* parsing step.
|
||||
*/
|
||||
void finalizeSfzLoad();
|
||||
/**
|
||||
* @brief Sets the tuning from a Scala file loaded from the file system.
|
||||
*
|
||||
|
|
@ -208,24 +190,6 @@ public:
|
|||
* @return const Region*
|
||||
*/
|
||||
const Region* getRegionById(NumericId<Region> id) const noexcept;
|
||||
/**
|
||||
* @brief Find the voice which is associated with the given identifier.
|
||||
*
|
||||
* @param id
|
||||
* @return const Voice*
|
||||
*/
|
||||
const Voice* getVoiceById(NumericId<Voice> id) const noexcept;
|
||||
/**
|
||||
* @brief Find the voice which is associated with the given identifier.
|
||||
*
|
||||
* @param id
|
||||
* @return Voice*
|
||||
*/
|
||||
Voice* getVoiceById(NumericId<Voice> id) noexcept
|
||||
{
|
||||
return const_cast<Voice*>(
|
||||
const_cast<const Synth*>(this)->getVoiceById(id));
|
||||
}
|
||||
/**
|
||||
* @brief Get a raw view into a specific region. This is mostly used
|
||||
* for testing.
|
||||
|
|
@ -245,6 +209,7 @@ public:
|
|||
/**
|
||||
* @brief Get a raw view into a specific effect bus. This is mostly used
|
||||
* for testing.
|
||||
* You'll need to include "Effects.h" to resolve the forward declaration.
|
||||
*
|
||||
* @param idx
|
||||
* @return const EffectBus*
|
||||
|
|
@ -253,6 +218,7 @@ public:
|
|||
/**
|
||||
* @brief Get a raw view into a specific set of regions. This is mostly used
|
||||
* for testing.
|
||||
* You'll need to include "RegionSet.h" to resolve the forward declaration.
|
||||
*
|
||||
* @param idx
|
||||
* @return const RegionSet*
|
||||
|
|
@ -261,6 +227,7 @@ public:
|
|||
/**
|
||||
* @brief Get a raw view into a specific polyphony group. This is mostly used
|
||||
* for testing.
|
||||
* You'll need to include "PolyphonyGroup.h" to resolve the forward declaration.
|
||||
*
|
||||
* @param idx
|
||||
* @return const PolyphonyGroup*
|
||||
|
|
@ -370,29 +337,13 @@ public:
|
|||
* @param normValue the normalized cc value, in domain 0 to 1
|
||||
*/
|
||||
void hdcc(int delay, int ccNumber, float normValue) noexcept;
|
||||
private:
|
||||
/**
|
||||
* @brief Set the initial value of a controller and send it to the synth
|
||||
* @brief Get the current value of a controller under the current instrument
|
||||
*
|
||||
* @param ccNumber the cc number
|
||||
* @param ccValue the cc value
|
||||
* @return the current value
|
||||
*/
|
||||
void initCc(int ccNumber, uint8_t ccValue) noexcept;
|
||||
/**
|
||||
* @brief Set the initial value of a controller and send it to the synth
|
||||
*
|
||||
* @param ccNumber the cc number
|
||||
* @param normValue the normalized cc value, in domain 0 to 1
|
||||
*/
|
||||
void initHdcc(int ccNumber, float normValue) noexcept;
|
||||
public:
|
||||
/**
|
||||
* @brief Get the initial value of a controller under the current instrument
|
||||
*
|
||||
* @param ccNumber the cc number
|
||||
* @return the initial value
|
||||
*/
|
||||
float getHdccInit(int ccNumber);
|
||||
float getHdcc(int ccNumber);
|
||||
/**
|
||||
* @brief Send a pitch bend event to the synth
|
||||
*
|
||||
|
|
@ -475,15 +426,6 @@ public:
|
|||
* @param numVoices
|
||||
*/
|
||||
void setNumVoices(int numVoices) noexcept;
|
||||
/**
|
||||
* @brief Trigger a garbage collection, which removes the samples that are
|
||||
* loaded by the FilePool after being requested by the voices. This does
|
||||
* not concern the preloaded samples, only the samples loaded to be played
|
||||
* fully. This function is run regularly in a background thread so normally
|
||||
* you should not need to call it explicitely.
|
||||
*
|
||||
*/
|
||||
void garbageCollect() noexcept;
|
||||
|
||||
/**
|
||||
* @brief Set the oversampling factor to a new value.
|
||||
|
|
@ -551,8 +493,8 @@ public:
|
|||
*/
|
||||
void disableFreeWheeling() noexcept;
|
||||
|
||||
Resources& getResources() noexcept { return resources; }
|
||||
const Resources& getResources() const noexcept { return resources; }
|
||||
Resources& getResources() noexcept;
|
||||
const Resources& getResources() const noexcept;
|
||||
|
||||
/**
|
||||
* @brief Check if the SFZ should be reloaded.
|
||||
|
|
@ -604,26 +546,26 @@ public:
|
|||
*
|
||||
* @return A reference to the parser.
|
||||
*/
|
||||
Parser& getParser() noexcept { return parser; }
|
||||
Parser& getParser() noexcept;
|
||||
/**
|
||||
* @brief Get the parser.
|
||||
*
|
||||
* @return A reference to the parser.
|
||||
*/
|
||||
const Parser& getParser() const noexcept { return parser; }
|
||||
const Parser& getParser() const noexcept;
|
||||
|
||||
/**
|
||||
* @brief Get the key labels, if any
|
||||
*
|
||||
* @return const std::vector<NoteNamePair>&
|
||||
*/
|
||||
const std::vector<NoteNamePair>& getKeyLabels() const noexcept { return keyLabels; }
|
||||
const std::vector<NoteNamePair>& getKeyLabels() const noexcept;
|
||||
/**
|
||||
* @brief Get the CC labels, if any
|
||||
*
|
||||
* @return const std::vector<NoteNamePair>&
|
||||
*/
|
||||
const std::vector<CCNamePair>& getCCLabels() const noexcept { return ccLabels; }
|
||||
const std::vector<CCNamePair>& getCCLabels() const noexcept;
|
||||
|
||||
/**
|
||||
* @brief Get the used CCs
|
||||
|
|
@ -632,332 +574,9 @@ public:
|
|||
*/
|
||||
std::bitset<config::numCCs> getUsedCCs() const noexcept;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief The voice callback which is called during a change of state.
|
||||
*/
|
||||
void onVoiceStateChanged(NumericId<Voice> idNumber, Voice::State state) override;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief The parser callback; this is called by the parent object each time
|
||||
* a new region, group, master, global, curve or control set of opcodes
|
||||
* appears in the parser
|
||||
*
|
||||
* @param header the header for the set of opcodes
|
||||
* @param members the opcode members
|
||||
*/
|
||||
void onParseFullBlock(const std::string& header, const std::vector<Opcode>& members) override;
|
||||
|
||||
/**
|
||||
* @brief The parser callback when an error occurs.
|
||||
*/
|
||||
void onParseError(const SourceRange& range, const std::string& message) override;
|
||||
|
||||
/**
|
||||
* @brief The parser callback when a warning occurs.
|
||||
*/
|
||||
void onParseWarning(const SourceRange& range, const std::string& message) override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief change the group maximum polyphony
|
||||
*
|
||||
* @param groupIdx the group index
|
||||
* @param polyphone the max polyphony
|
||||
*/
|
||||
void setGroupPolyphony(unsigned groupIdx, unsigned polyphony) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Reset all CCs; to be used on CC 121
|
||||
*
|
||||
* @param delay the delay for the controller reset
|
||||
*
|
||||
*/
|
||||
void resetAllControllers(int delay) noexcept;
|
||||
|
||||
int numGroups { 0 };
|
||||
int numMasters { 0 };
|
||||
|
||||
/**
|
||||
* @brief Remove all regions, resets all voices and clears everything
|
||||
* to bring back the synth in its original state.
|
||||
*
|
||||
* The callback mutex should be taken to call this function.
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* @brief Helper function to dispatch <global> opcodes
|
||||
*
|
||||
* @param members the opcodes of the <global> block
|
||||
*/
|
||||
void handleGlobalOpcodes(const std::vector<Opcode>& members);
|
||||
/**
|
||||
* @brief Helper function to dispatch <master> opcodes
|
||||
*
|
||||
* @param members the opcodes of the <master> block
|
||||
*/
|
||||
void handleMasterOpcodes(const std::vector<Opcode>& members);
|
||||
/**
|
||||
* @brief Helper function to dispatch <group> opcodes
|
||||
*
|
||||
* @param members the opcodes of the <group> block
|
||||
*/
|
||||
void handleGroupOpcodes(const std::vector<Opcode>& members, const std::vector<Opcode>& masterMembers);
|
||||
/**
|
||||
* @brief Helper function to dispatch <control> opcodes
|
||||
*
|
||||
* @param members the opcodes of the <control> block
|
||||
*/
|
||||
void handleControlOpcodes(const std::vector<Opcode>& members);
|
||||
/**
|
||||
* @brief Helper function to dispatch <effect> opcodes
|
||||
*
|
||||
* @param members the opcodes of the <effect> block
|
||||
*/
|
||||
void handleEffectOpcodes(const std::vector<Opcode>& members);
|
||||
/**
|
||||
* @brief Helper function to merge all the currently active opcodes
|
||||
* as set by the successive callbacks and create a new region to store
|
||||
* in the synth.
|
||||
*
|
||||
* @param regionOpcodes the opcodes that are specific to the region
|
||||
*/
|
||||
void buildRegion(const std::vector<Opcode>& regionOpcodes);
|
||||
/**
|
||||
* @brief Resets and possibly changes the number of voices (polyphony) in
|
||||
* the synth.
|
||||
*
|
||||
* @param numVoices
|
||||
*/
|
||||
void resetVoices(int numVoices);
|
||||
/**
|
||||
* @brief Make the stored settings take effect in all the voices
|
||||
*/
|
||||
void applySettingsPerVoice();
|
||||
|
||||
/**
|
||||
* @brief Establish all connections of the modulation matrix.
|
||||
*/
|
||||
void setupModMatrix();
|
||||
|
||||
/**
|
||||
* @brief Get the modification time of all included sfz files
|
||||
*
|
||||
* @return fs::file_time_type
|
||||
*/
|
||||
fs::file_time_type checkModificationTime();
|
||||
|
||||
/**
|
||||
* @brief Check all regions and start voices for note on events
|
||||
*
|
||||
* @param delay
|
||||
* @param noteNumber
|
||||
* @param velocity
|
||||
*/
|
||||
void noteOnDispatch(int delay, int noteNumber, float velocity) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Check all regions and start voices for note off events
|
||||
*
|
||||
* @param delay
|
||||
* @param noteNumber
|
||||
* @param velocity
|
||||
*/
|
||||
void noteOffDispatch(int delay, int noteNumber, float velocity) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Check all regions and start voices for cc events
|
||||
*
|
||||
* @param delay
|
||||
* @param ccNumber
|
||||
* @param value
|
||||
*/
|
||||
void ccDispatch(int delay, int ccNumber, float value) noexcept;
|
||||
|
||||
template<class T>
|
||||
static void updateUsedCCsFromCCMap(std::bitset<sfz::config::numCCs>& usedCCs, const CCMap<T> map)
|
||||
{
|
||||
for (auto& mod : map)
|
||||
usedCCs[mod.cc] = true;
|
||||
}
|
||||
static void updateUsedCCsFromRegion(std::bitset<sfz::config::numCCs>& usedCCs, const Region& region);
|
||||
static void updateUsedCCsFromModulations(std::bitset<sfz::config::numCCs>& usedCCs, const ModMatrix& mm);
|
||||
|
||||
// Opcode memory; these are used to build regions, as a new region
|
||||
// will integrate opcodes from the group, master and global block
|
||||
std::vector<Opcode> globalOpcodes;
|
||||
std::vector<Opcode> masterOpcodes;
|
||||
std::vector<Opcode> groupOpcodes;
|
||||
|
||||
/**
|
||||
* @brief Find a voice that is not currently playing
|
||||
*
|
||||
* @return Voice*
|
||||
*/
|
||||
Voice* findFreeVoice() noexcept;
|
||||
|
||||
// Names for the CC and notes as set by label_cc and label_key
|
||||
std::vector<CCNamePair> ccLabels;
|
||||
std::vector<NoteNamePair> keyLabels;
|
||||
std::vector<NoteNamePair> keyswitchLabels;
|
||||
|
||||
// Set as sw_default if present in the file
|
||||
absl::optional<uint8_t> currentSwitch;
|
||||
std::vector<std::string> unknownOpcodes;
|
||||
using RegionViewVector = std::vector<Region*>;
|
||||
using VoiceViewVector = std::vector<Voice*>;
|
||||
using VoicePtr = std::unique_ptr<Voice>;
|
||||
using RegionPtr = std::unique_ptr<Region>;
|
||||
using RegionSetPtr = std::unique_ptr<RegionSet>;
|
||||
std::vector<RegionPtr> regions;
|
||||
std::vector<VoicePtr> voices;
|
||||
|
||||
// These are more general "groups" than sfz and encapsulates the full hierarchy
|
||||
RegionSet* currentSet { nullptr };
|
||||
std::vector<RegionSetPtr> sets;
|
||||
// This region set holds the engine set of voices, which tries to respect the required
|
||||
// engine polyphony
|
||||
RegionSetPtr engineSet;
|
||||
|
||||
// These are the `group=` groups where you can off voices
|
||||
std::vector<PolyphonyGroup> polyphonyGroups;
|
||||
|
||||
// Views to speed up iteration over the regions and voices when events
|
||||
// occur in the audio callback
|
||||
VoiceViewVector tempPolyphonyArray;
|
||||
VoiceViewVector voiceViewArray;
|
||||
VoiceStealing stealer;
|
||||
|
||||
/**
|
||||
* @brief Check the region polyphony, releasing voices if necessary
|
||||
*
|
||||
* @param region
|
||||
* @param delay
|
||||
*/
|
||||
void checkRegionPolyphony(const Region* region, int delay) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Check the note polyphony, releasing voices if necessary
|
||||
*
|
||||
* @param region
|
||||
* @param delay
|
||||
* @param triggerEvent
|
||||
*/
|
||||
void checkNotePolyphony(const Region* region, int delay, const TriggerEvent& triggerEvent) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Check the group polyphony, releasing voices if necessary
|
||||
*
|
||||
* @param region
|
||||
* @param delay
|
||||
*/
|
||||
void checkGroupPolyphony(const Region* region, int delay) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Check the region set polyphony at all levels, releasing voices if necessary
|
||||
*
|
||||
* @param region
|
||||
* @param delay
|
||||
*/
|
||||
void checkSetPolyphony(const Region* region, int delay) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Check the engine polyphony, fast releasing voices if necessary
|
||||
*
|
||||
* @param delay
|
||||
*/
|
||||
void checkEnginePolyphony(int delay) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Start a voice for a specific region.
|
||||
* This will do the needed polyphony checks and voice stealing.
|
||||
*
|
||||
* @param region
|
||||
* @param delay
|
||||
* @param triggerEvent
|
||||
* @param ring
|
||||
*/
|
||||
void startVoice(Region* region, int delay, const TriggerEvent& triggerEvent, SisterVoiceRingBuilder& ring) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Start all delayed release voices of the region if necessary
|
||||
*
|
||||
* @param region
|
||||
* @param delay
|
||||
* @param ring
|
||||
*/
|
||||
void startDelayedReleaseVoices(Region* region, int delay, SisterVoiceRingBuilder& ring) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Check if a playing voice matches the release region
|
||||
*
|
||||
* @param releaseRegion
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
bool playingAttackVoice(const Region* releaseRegion) noexcept;
|
||||
|
||||
std::array<RegionViewVector, 128> lastKeyswitchLists;
|
||||
std::array<RegionViewVector, 128> downKeyswitchLists;
|
||||
std::array<RegionViewVector, 128> upKeyswitchLists;
|
||||
RegionViewVector previousKeyswitchLists;
|
||||
std::array<RegionViewVector, 128> noteActivationLists;
|
||||
std::array<RegionViewVector, config::numCCs> ccActivationLists;
|
||||
|
||||
// Effect factory and buses
|
||||
EffectFactory effectFactory;
|
||||
typedef std::unique_ptr<EffectBus> EffectBusPtr;
|
||||
std::vector<EffectBusPtr> effectBuses; // 0 is "main", 1-N are "fx1"-"fxN"
|
||||
|
||||
int samplesPerBlock { config::defaultSamplesPerBlock };
|
||||
float sampleRate { config::defaultSampleRate };
|
||||
float volume { Default::globalVolume };
|
||||
int numRequiredVoices { config::numVoices };
|
||||
int numActualVoices { static_cast<int>(config::numVoices * config::overflowVoiceMultiplier) };
|
||||
int activeVoices { 0 };
|
||||
Oversampling oversamplingFactor { config::defaultOversamplingFactor };
|
||||
|
||||
// Distribution used to generate random value for the *rand opcodes
|
||||
std::uniform_real_distribution<float> randNoteDistribution { 0, 1 };
|
||||
|
||||
SpinMutex callbackGuard;
|
||||
|
||||
// Singletons passed as references to the voices
|
||||
Resources resources;
|
||||
|
||||
// Control opcodes
|
||||
std::string defaultPath { "" };
|
||||
int noteOffset { 0 };
|
||||
int octaveOffset { 0 };
|
||||
|
||||
// Modulation source generators
|
||||
std::unique_ptr<ControllerSource> genController;
|
||||
std::unique_ptr<LFOSource> genLFO;
|
||||
std::unique_ptr<FlexEnvelopeSource> genFlexEnvelope;
|
||||
std::unique_ptr<ADSREnvelopeSource> genADSREnvelope;
|
||||
|
||||
// Settings per voice
|
||||
struct SettingsPerVoice {
|
||||
size_t maxFilters { 0 };
|
||||
size_t maxEQs { 0 };
|
||||
size_t maxLFOs { 0 };
|
||||
size_t maxFlexEGs { 0 };
|
||||
bool havePitchEG { false };
|
||||
bool haveFilterEG { false };
|
||||
};
|
||||
SettingsPerVoice settingsPerVoice;
|
||||
|
||||
// Controller initial values
|
||||
std::array<float, config::numCCs> ccInitialValues;
|
||||
|
||||
Duration dispatchDuration { 0 };
|
||||
|
||||
std::chrono::time_point<std::chrono::high_resolution_clock> lastGarbageCollection;
|
||||
|
||||
Parser parser;
|
||||
fs::file_time_type modificationTime { };
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> impl_;
|
||||
|
||||
LEAK_DETECTOR(Synth);
|
||||
};
|
||||
|
|
|
|||
1202
src/sfizz/Voice.cpp
1202
src/sfizz/Voice.cpp
File diff suppressed because it is too large
Load diff
|
|
@ -5,24 +5,14 @@
|
|||
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
|
||||
|
||||
#pragma once
|
||||
#include "TriggerEvent.h"
|
||||
#include "Config.h"
|
||||
#include "ADSREnvelope.h"
|
||||
#include "HistoricalBuffer.h"
|
||||
#include "TriggerEvent.h"
|
||||
#include "Region.h"
|
||||
#include "AudioBuffer.h"
|
||||
#include "Resources.h"
|
||||
#include "FilterPool.h"
|
||||
#include "EQPool.h"
|
||||
#include "Smoothers.h"
|
||||
#include "AudioSpan.h"
|
||||
#include "LeakDetector.h"
|
||||
#include "OnePoleFilter.h"
|
||||
#include "PowerFollower.h"
|
||||
#include "utility/NumericId.h"
|
||||
#include "absl/types/span.h"
|
||||
#include <memory>
|
||||
#include <random>
|
||||
|
||||
namespace sfz {
|
||||
enum InterpolatorModel : int;
|
||||
|
|
@ -44,16 +34,16 @@ public:
|
|||
* @param midiState
|
||||
*/
|
||||
Voice(int voiceNumber, Resources& resources);
|
||||
|
||||
~Voice();
|
||||
Voice(const Voice& other) = delete;
|
||||
Voice& operator=(const Voice& other) = delete;
|
||||
Voice(Voice&& other);
|
||||
Voice& operator=(Voice&& other);
|
||||
|
||||
/**
|
||||
* @brief Get the unique identifier of this voice in a synth
|
||||
*/
|
||||
NumericId<Voice> getId() const noexcept
|
||||
{
|
||||
return id;
|
||||
}
|
||||
NumericId<Voice> getId() const noexcept;
|
||||
|
||||
enum class State {
|
||||
idle,
|
||||
|
|
@ -69,12 +59,12 @@ public:
|
|||
/**
|
||||
* @brief Return true if the voice is to be cleaned up (zombie state)
|
||||
*/
|
||||
bool toBeCleanedUp() const { return state == State::cleanMeUp; }
|
||||
bool toBeCleanedUp() const;
|
||||
|
||||
/**
|
||||
* @brief Sets the listener which is called when the voice state changes.
|
||||
*/
|
||||
void setStateListener(StateListener *l) noexcept { stateListener = l; }
|
||||
void setStateListener(StateListener *l) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Change the sample rate of the voice. This is used to compute all
|
||||
|
|
@ -98,13 +88,13 @@ public:
|
|||
*
|
||||
* @return float
|
||||
*/
|
||||
float getSampleRate() const noexcept { return sampleRate; }
|
||||
float getSampleRate() const noexcept;
|
||||
/**
|
||||
* @brief Get the expected block size.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
int getSamplesPerBlock() const noexcept { return samplesPerBlock; }
|
||||
int getSamplesPerBlock() const noexcept;
|
||||
|
||||
/**
|
||||
* @brief Start playing a region after a short delay for different triggers (note on, off, cc)
|
||||
|
|
@ -199,7 +189,7 @@ public:
|
|||
*
|
||||
* @return int
|
||||
*/
|
||||
const TriggerEvent& getTriggerEvent() const noexcept { return triggerEvent; }
|
||||
const TriggerEvent& getTriggerEvent() const noexcept;
|
||||
|
||||
/**
|
||||
* @brief Reset the voice to its initial values
|
||||
|
|
@ -232,14 +222,14 @@ public:
|
|||
*
|
||||
* @return Voice*
|
||||
*/
|
||||
Voice* getNextSisterVoice() const noexcept { return nextSisterVoice; };
|
||||
Voice* getNextSisterVoice() const noexcept { return nextSisterVoice_; };
|
||||
|
||||
/**
|
||||
* @brief Get the previous sister voice in the ring
|
||||
*
|
||||
* @return Voice*
|
||||
*/
|
||||
Voice* getPreviousSisterVoice() const noexcept { return previousSisterVoice; };
|
||||
Voice* getPreviousSisterVoice() const noexcept { return previousSisterVoice_; };
|
||||
|
||||
/**
|
||||
* @brief Get the mean squared power of the last rendered block. This is used
|
||||
|
|
@ -266,19 +256,19 @@ public:
|
|||
*
|
||||
* @return
|
||||
*/
|
||||
const Region* getRegion() const noexcept { return region; }
|
||||
const Region* getRegion() const noexcept;
|
||||
/**
|
||||
* @brief Get the LFO designated by the given index
|
||||
*
|
||||
* @param index
|
||||
*/
|
||||
LFO* getLFO(size_t index) { return lfos[index].get(); }
|
||||
LFO* getLFO(size_t index);
|
||||
/**
|
||||
* @brief Get the Flex EG designated by the given index
|
||||
*
|
||||
* @param index
|
||||
*/
|
||||
FlexEnvelope* getFlexEG(size_t index) { return flexEGs[index].get(); }
|
||||
FlexEnvelope* getFlexEG(size_t index);
|
||||
/**
|
||||
* @brief Set the max number of filters per voice
|
||||
*
|
||||
|
|
@ -337,250 +327,42 @@ public:
|
|||
*
|
||||
* @return
|
||||
*/
|
||||
int getAge() const noexcept { return age; }
|
||||
int getAge() const noexcept;
|
||||
|
||||
Duration getLastDataDuration() const noexcept { return dataDuration; }
|
||||
Duration getLastAmplitudeDuration() const noexcept { return amplitudeDuration; }
|
||||
Duration getLastFilterDuration() const noexcept { return filterDuration; }
|
||||
Duration getLastPanningDuration() const noexcept { return panningDuration; }
|
||||
Duration getLastDataDuration() const noexcept;
|
||||
Duration getLastAmplitudeDuration() const noexcept;
|
||||
Duration getLastFilterDuration() const noexcept;
|
||||
Duration getLastPanningDuration() const noexcept;
|
||||
|
||||
/**
|
||||
* @brief Get the SFZv1 amplitude EG, if existing
|
||||
*/
|
||||
ADSREnvelope<float>* getAmplitudeEG() { return &egAmplitude; }
|
||||
ADSREnvelope<float>* getAmplitudeEG();
|
||||
/**
|
||||
* @brief Get the SFZv1 pitch EG, if existing
|
||||
*/
|
||||
ADSREnvelope<float>* getPitchEG() { return egPitch.get(); }
|
||||
ADSREnvelope<float>* getPitchEG();
|
||||
/**
|
||||
* @brief Get the SFZv1 filter EG, if existing
|
||||
*/
|
||||
ADSREnvelope<float>* getFilterEG() { return egFilter.get(); }
|
||||
ADSREnvelope<float>* getFilterEG();
|
||||
|
||||
/**
|
||||
* @brief Get the trigger event
|
||||
*/
|
||||
const TriggerEvent& getTriggerEvent() { return triggerEvent; }
|
||||
const TriggerEvent& getTriggerEvent();
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Fill a span with data from a file source. This is the first step
|
||||
* in rendering each block of data.
|
||||
*
|
||||
* @param buffer
|
||||
*/
|
||||
void fillWithData(AudioSpan<float> buffer) noexcept;
|
||||
/**
|
||||
* @brief Fill a span with data from a generator source. This is the first step
|
||||
* in rendering each block of data.
|
||||
*
|
||||
* @param buffer
|
||||
*/
|
||||
void fillWithGenerator(AudioSpan<float> buffer) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Fill a destination with an interpolated source.
|
||||
*
|
||||
* @param source the source sample
|
||||
* @param dest the destination buffer
|
||||
* @param indices the integral parts of the source positions
|
||||
* @param coeffs the fractional parts of the source positions
|
||||
*/
|
||||
template <InterpolatorModel M, bool Adding>
|
||||
static void fillInterpolated(
|
||||
const AudioSpan<const float>& source, const AudioSpan<float>& dest,
|
||||
absl::Span<const int> indices, absl::Span<const float> coeffs,
|
||||
absl::Span<const float> addingGains);
|
||||
|
||||
/**
|
||||
* @brief Fill a destination with an interpolated source, selecting
|
||||
* interpolation type dynamically by quality level.
|
||||
*
|
||||
* @param source the source sample
|
||||
* @param dest the destination buffer
|
||||
* @param indices the integral parts of the source positions
|
||||
* @param coeffs the fractional parts of the source positions
|
||||
* @param quality the quality level 1-10
|
||||
*/
|
||||
template <bool Adding>
|
||||
static void fillInterpolatedWithQuality(
|
||||
const AudioSpan<const float>& source, const AudioSpan<float>& dest,
|
||||
absl::Span<const int> indices, absl::Span<const float> coeffs,
|
||||
absl::Span<const float> addingGains, int quality);
|
||||
|
||||
/**
|
||||
* @brief Get a S-shaped curve that is applicable to loop crossfading.
|
||||
*/
|
||||
static const Curve& getSCurve();
|
||||
|
||||
/**
|
||||
* @brief Compute the amplitude envelope, applied as a gain to a mono
|
||||
* or stereo buffer
|
||||
*
|
||||
* @param modulationSpan
|
||||
*/
|
||||
void amplitudeEnvelope(absl::Span<float> modulationSpan) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Apply the crossfade envelope to a span.
|
||||
*
|
||||
* @param modulationSpan
|
||||
*/
|
||||
void applyCrossfades(absl::Span<float> modulationSpan) noexcept;
|
||||
void resetCrossfades() noexcept;
|
||||
|
||||
/**
|
||||
* @brief Amplitude stage for a mono source
|
||||
*
|
||||
* @param buffer
|
||||
*/
|
||||
void ampStageMono(AudioSpan<float> buffer) noexcept;
|
||||
/**
|
||||
* @brief Amplitude stage for a stereo source
|
||||
*
|
||||
* @param buffer
|
||||
*/
|
||||
void ampStageStereo(AudioSpan<float> buffer) noexcept;
|
||||
/**
|
||||
* @brief Amplitude stage for a mono source
|
||||
*
|
||||
* @param buffer
|
||||
*/
|
||||
void panStageMono(AudioSpan<float> buffer) noexcept;
|
||||
void panStageStereo(AudioSpan<float> buffer) noexcept;
|
||||
/**
|
||||
* @brief Amplitude stage for a mono source
|
||||
*
|
||||
* @param buffer
|
||||
*/
|
||||
void filterStageMono(AudioSpan<float> buffer) noexcept;
|
||||
void filterStageStereo(AudioSpan<float> buffer) noexcept;
|
||||
/**
|
||||
* @brief Compute the pitch envelope. This envelope is meant to multiply
|
||||
* the frequency parameter for each sample (which translates to floating
|
||||
* point intervals for sample-based voices, or phases for generators)
|
||||
*
|
||||
* @param pitchSpan
|
||||
*/
|
||||
void pitchEnvelope(absl::Span<float> pitchSpan) noexcept;
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> impl_;
|
||||
|
||||
/**
|
||||
* @brief Remove the voice from the sister ring
|
||||
*
|
||||
*/
|
||||
void removeVoiceFromRing() noexcept;
|
||||
|
||||
/**
|
||||
* @brief Initialize frequency and gain coefficients for the oscillators.
|
||||
*/
|
||||
void setupOscillatorUnison();
|
||||
void updateChannelPowers(AudioSpan<float> buffer);
|
||||
|
||||
/**
|
||||
* @brief Modify the voice state and notify any listeners.
|
||||
*/
|
||||
void switchState(State s);
|
||||
|
||||
/**
|
||||
* @brief Save the modulation targets to avoid recomputing them in every callback.
|
||||
* Must be called during startVoice() ideally.
|
||||
*/
|
||||
void saveModulationTargets(const Region* region) noexcept;
|
||||
|
||||
const NumericId<Voice> id;
|
||||
StateListener* stateListener = nullptr;
|
||||
|
||||
Region* region { nullptr };
|
||||
|
||||
State state { State::idle };
|
||||
bool noteIsOff { false };
|
||||
|
||||
TriggerEvent triggerEvent;
|
||||
absl::optional<int> triggerDelay;
|
||||
|
||||
float speedRatio { 1.0 };
|
||||
float pitchRatio { 1.0 };
|
||||
float baseVolumedB { 0.0 };
|
||||
float baseGain { 1.0 };
|
||||
float baseFrequency { 440.0 };
|
||||
|
||||
float floatPositionOffset { 0.0f };
|
||||
int sourcePosition { 0 };
|
||||
int initialDelay { 0 };
|
||||
int age { 0 };
|
||||
struct {
|
||||
int start { 0 };
|
||||
int end { 0 };
|
||||
int size { 0 };
|
||||
int xfSize { 0 };
|
||||
int xfOutStart { 0 };
|
||||
int xfInStart { 0 };
|
||||
} loop;
|
||||
/**
|
||||
* @brief Reset the loop information
|
||||
*
|
||||
*/
|
||||
void resetLoopInformation() noexcept;
|
||||
/**
|
||||
* @brief Read the loop information data from the region.
|
||||
* This requires that the region and promise is properly set.
|
||||
*
|
||||
*/
|
||||
void updateLoopInformation() noexcept;
|
||||
|
||||
FileDataHolder currentPromise;
|
||||
|
||||
int samplesPerBlock { config::defaultSamplesPerBlock };
|
||||
float sampleRate { config::defaultSampleRate };
|
||||
|
||||
Resources& resources;
|
||||
|
||||
std::vector<FilterHolder> filters;
|
||||
std::vector<EQHolder> equalizers;
|
||||
std::vector<std::unique_ptr<LFO>> lfos;
|
||||
std::vector<std::unique_ptr<FlexEnvelope>> flexEGs;
|
||||
|
||||
ADSREnvelope<float> egAmplitude;
|
||||
std::unique_ptr<ADSREnvelope<float>> egPitch;
|
||||
std::unique_ptr<ADSREnvelope<float>> egFilter;
|
||||
float bendStepFactor { centsFactor(1) };
|
||||
|
||||
WavetableOscillator waveOscillators[config::oscillatorsPerVoice];
|
||||
|
||||
// unison of oscillators
|
||||
unsigned waveUnisonSize { 0 };
|
||||
float waveDetuneRatio[config::oscillatorsPerVoice] {};
|
||||
float waveLeftGain[config::oscillatorsPerVoice] {};
|
||||
float waveRightGain[config::oscillatorsPerVoice] {};
|
||||
|
||||
Duration dataDuration;
|
||||
Duration amplitudeDuration;
|
||||
Duration panningDuration;
|
||||
Duration filterDuration;
|
||||
|
||||
Voice* nextSisterVoice { this };
|
||||
Voice* previousSisterVoice { this };
|
||||
|
||||
fast_real_distribution<float> uniformNoiseDist { -config::uniformNoiseBounds, config::uniformNoiseBounds };
|
||||
fast_gaussian_generator<float> gaussianNoiseDist { 0.0f, config::noiseVariance };
|
||||
|
||||
Smoother gainSmoother;
|
||||
Smoother bendSmoother;
|
||||
Smoother xfadeSmoother;
|
||||
void resetSmoothers() noexcept;
|
||||
|
||||
ModMatrix::TargetId masterAmplitudeTarget;
|
||||
ModMatrix::TargetId amplitudeTarget;
|
||||
ModMatrix::TargetId volumeTarget;
|
||||
ModMatrix::TargetId panTarget;
|
||||
ModMatrix::TargetId positionTarget;
|
||||
ModMatrix::TargetId widthTarget;
|
||||
ModMatrix::TargetId pitchTarget;
|
||||
ModMatrix::TargetId oscillatorDetuneTarget;
|
||||
ModMatrix::TargetId oscillatorModDepthTarget;
|
||||
|
||||
bool followPower { false };
|
||||
PowerFollower powerFollower;
|
||||
Voice* nextSisterVoice_ { this };
|
||||
Voice* previousSisterVoice_ { this };
|
||||
|
||||
LEAK_DETECTOR(Voice);
|
||||
};
|
||||
|
|
|
|||
68
src/sfizz/VoiceList.h
Normal file
68
src/sfizz/VoiceList.h
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
// 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 "Voice.h"
|
||||
#include <vector>
|
||||
|
||||
namespace sfz {
|
||||
|
||||
struct VoiceList
|
||||
{
|
||||
/**
|
||||
* @brief Find the voice which is associated with the given identifier.
|
||||
*
|
||||
* @param id
|
||||
* @return const Voice*
|
||||
*/
|
||||
const Voice* getVoiceById(NumericId<Voice> id) const noexcept
|
||||
{
|
||||
const size_t size = list.size();
|
||||
|
||||
if (size == 0 || !id.valid())
|
||||
return nullptr;
|
||||
|
||||
// search a sequence of ordered identifiers with potential gaps
|
||||
size_t index = static_cast<size_t>(id.number());
|
||||
index = std::min(index, size - 1);
|
||||
|
||||
while (index > 0 && list[index].getId().number() > id.number())
|
||||
--index;
|
||||
|
||||
return (list[index].getId() == id) ? &list[index] : nullptr;
|
||||
}
|
||||
|
||||
Voice* getVoiceById(NumericId<Voice> id) noexcept
|
||||
{
|
||||
return const_cast<Voice*>(
|
||||
const_cast<const VoiceList*>(this)->getVoiceById(id));
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
for (auto& voice : list)
|
||||
voice.reset();
|
||||
}
|
||||
|
||||
typename std::vector<Voice>::iterator begin() { return list.begin(); }
|
||||
typename std::vector<Voice>::const_iterator cbegin() const { return list.cbegin(); }
|
||||
typename std::vector<Voice>::iterator end() { return list.end(); }
|
||||
typename std::vector<Voice>::const_iterator cend() const { return list.cend(); }
|
||||
typename std::vector<Voice>::reference operator[] (size_t n) { return list[n]; }
|
||||
typename std::vector<Voice>::const_reference operator[] (size_t n) const { return list[n]; }
|
||||
typename std::vector<Voice>::reference back() { return list.back(); }
|
||||
typename std::vector<Voice>::const_reference back() const { return list.back(); }
|
||||
size_t size() const { return list.size(); }
|
||||
void clear() { list.clear(); }
|
||||
void reserve(size_t n) { list.reserve(n); }
|
||||
template< class... Args >
|
||||
void emplace_back(Args&&... args) { list.emplace_back(std::forward<Args>(args)...); }
|
||||
private:
|
||||
std::vector<Voice> list;
|
||||
};
|
||||
|
||||
} // namespace sfz
|
||||
|
|
@ -15,16 +15,14 @@
|
|||
|
||||
namespace sfz {
|
||||
|
||||
ADSREnvelopeSource::ADSREnvelopeSource(Synth &synth)
|
||||
: synth_(&synth)
|
||||
ADSREnvelopeSource::ADSREnvelopeSource(VoiceList& list, MidiState& state)
|
||||
: voiceList_(list), midiState_(state)
|
||||
{
|
||||
}
|
||||
|
||||
void ADSREnvelopeSource::init(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay)
|
||||
{
|
||||
Synth& synth = *synth_;
|
||||
|
||||
Voice* voice = synth.getVoiceById(voiceId);
|
||||
Voice* voice = voiceList_.getVoiceById(voiceId);
|
||||
if (!voice) {
|
||||
ASSERTFALSE;
|
||||
return;
|
||||
|
|
@ -55,17 +53,14 @@ void ADSREnvelopeSource::init(const ModKey& sourceKey, NumericId<Voice> voiceId,
|
|||
return;
|
||||
}
|
||||
|
||||
Resources& resources = synth.getResources();
|
||||
const TriggerEvent& triggerEvent = voice->getTriggerEvent();
|
||||
const float sampleRate = voice->getSampleRate();
|
||||
eg->reset(*desc, *region, resources.midiState, delay, triggerEvent.value, sampleRate);
|
||||
eg->reset(*desc, *region, midiState_, delay, triggerEvent.value, sampleRate);
|
||||
}
|
||||
|
||||
void ADSREnvelopeSource::release(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay)
|
||||
{
|
||||
Synth& synth = *synth_;
|
||||
|
||||
Voice* voice = synth.getVoiceById(voiceId);
|
||||
Voice* voice = voiceList_.getVoiceById(voiceId);
|
||||
if (!voice) {
|
||||
ASSERTFALSE;
|
||||
return;
|
||||
|
|
@ -96,9 +91,7 @@ void ADSREnvelopeSource::release(const ModKey& sourceKey, NumericId<Voice> voice
|
|||
|
||||
void ADSREnvelopeSource::generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl::Span<float> buffer)
|
||||
{
|
||||
Synth& synth = *synth_;
|
||||
|
||||
Voice* voice = synth.getVoiceById(voiceId);
|
||||
Voice* voice = voiceList_.getVoiceById(voiceId);
|
||||
if (!voice) {
|
||||
ASSERTFALSE;
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -6,19 +6,22 @@
|
|||
|
||||
#pragma once
|
||||
#include "../ModGenerator.h"
|
||||
#include "../../VoiceList.h"
|
||||
#include "../../MidiState.h"
|
||||
|
||||
namespace sfz {
|
||||
class Synth;
|
||||
|
||||
class ADSREnvelopeSource : public ModGenerator {
|
||||
public:
|
||||
explicit ADSREnvelopeSource(Synth &synth);
|
||||
explicit ADSREnvelopeSource(VoiceList &synth, MidiState& state);
|
||||
void init(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override;
|
||||
void release(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override;
|
||||
void generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl::Span<float> buffer) override;
|
||||
|
||||
private:
|
||||
Synth* synth_ = nullptr;
|
||||
VoiceList& voiceList_;
|
||||
MidiState& midiState_;
|
||||
};
|
||||
|
||||
} // namespace sfz
|
||||
|
|
|
|||
|
|
@ -14,17 +14,16 @@
|
|||
|
||||
namespace sfz {
|
||||
|
||||
FlexEnvelopeSource::FlexEnvelopeSource(Synth &synth)
|
||||
: synth_(&synth)
|
||||
FlexEnvelopeSource::FlexEnvelopeSource(VoiceList& list)
|
||||
: voiceList_(list)
|
||||
{
|
||||
}
|
||||
|
||||
void FlexEnvelopeSource::init(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay)
|
||||
{
|
||||
Synth& synth = *synth_;
|
||||
unsigned egIndex = sourceKey.parameters().N;
|
||||
|
||||
Voice* voice = synth.getVoiceById(voiceId);
|
||||
Voice* voice = voiceList_.getVoiceById(voiceId);
|
||||
if (!voice) {
|
||||
ASSERTFALSE;
|
||||
return;
|
||||
|
|
@ -49,10 +48,9 @@ void FlexEnvelopeSource::init(const ModKey& sourceKey, NumericId<Voice> voiceId,
|
|||
|
||||
void FlexEnvelopeSource::release(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay)
|
||||
{
|
||||
Synth& synth = *synth_;
|
||||
unsigned egIndex = sourceKey.parameters().N;
|
||||
|
||||
Voice* voice = synth.getVoiceById(voiceId);
|
||||
Voice* voice = voiceList_.getVoiceById(voiceId);
|
||||
if (!voice) {
|
||||
ASSERTFALSE;
|
||||
return;
|
||||
|
|
@ -70,10 +68,9 @@ void FlexEnvelopeSource::release(const ModKey& sourceKey, NumericId<Voice> voice
|
|||
|
||||
void FlexEnvelopeSource::generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl::Span<float> buffer)
|
||||
{
|
||||
Synth& synth = *synth_;
|
||||
unsigned egIndex = sourceKey.parameters().N;
|
||||
|
||||
Voice* voice = synth.getVoiceById(voiceId);
|
||||
Voice* voice = voiceList_.getVoiceById(voiceId);
|
||||
if (!voice) {
|
||||
ASSERTFALSE;
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -6,19 +6,20 @@
|
|||
|
||||
#pragma once
|
||||
#include "../ModGenerator.h"
|
||||
#include "../../VoiceList.h"
|
||||
|
||||
namespace sfz {
|
||||
class Synth;
|
||||
|
||||
class FlexEnvelopeSource : public ModGenerator {
|
||||
public:
|
||||
explicit FlexEnvelopeSource(Synth &synth);
|
||||
explicit FlexEnvelopeSource(VoiceList& list);
|
||||
void init(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override;
|
||||
void release(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override;
|
||||
void generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl::Span<float> buffer) override;
|
||||
|
||||
private:
|
||||
Synth* synth_ = nullptr;
|
||||
VoiceList& voiceList_;
|
||||
};
|
||||
|
||||
} // namespace sfz
|
||||
|
|
|
|||
|
|
@ -14,17 +14,16 @@
|
|||
|
||||
namespace sfz {
|
||||
|
||||
LFOSource::LFOSource(Synth &synth)
|
||||
: synth_(&synth)
|
||||
LFOSource::LFOSource(VoiceList& list)
|
||||
: voiceList_(list)
|
||||
{
|
||||
}
|
||||
|
||||
void LFOSource::init(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay)
|
||||
{
|
||||
Synth& synth = *synth_;
|
||||
unsigned lfoIndex = sourceKey.parameters().N;
|
||||
|
||||
Voice* voice = synth.getVoiceById(voiceId);
|
||||
Voice* voice = voiceList_.getVoiceById(voiceId);
|
||||
if (!voice) {
|
||||
ASSERTFALSE;
|
||||
return;
|
||||
|
|
@ -43,10 +42,9 @@ void LFOSource::init(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned
|
|||
|
||||
void LFOSource::generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl::Span<float> buffer)
|
||||
{
|
||||
Synth& synth = *synth_;
|
||||
const unsigned lfoIndex = sourceKey.parameters().N;
|
||||
|
||||
Voice* voice = synth.getVoiceById(voiceId);
|
||||
Voice* voice = voiceList_.getVoiceById(voiceId);
|
||||
if (!voice) {
|
||||
ASSERTFALSE;
|
||||
fill(buffer, 0.0f);
|
||||
|
|
|
|||
|
|
@ -6,18 +6,18 @@
|
|||
|
||||
#pragma once
|
||||
#include "../ModGenerator.h"
|
||||
|
||||
#include "../../VoiceList.h"
|
||||
namespace sfz {
|
||||
class Synth;
|
||||
|
||||
class LFOSource : public ModGenerator {
|
||||
public:
|
||||
explicit LFOSource(Synth &synth);
|
||||
explicit LFOSource(VoiceList &list);
|
||||
void init(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override;
|
||||
void generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl::Span<float> buffer) override;
|
||||
|
||||
private:
|
||||
Synth* synth_ = nullptr;
|
||||
VoiceList& voiceList_;
|
||||
};
|
||||
|
||||
} // namespace sfz
|
||||
|
|
|
|||
|
|
@ -10,6 +10,10 @@
|
|||
#include <absl/algorithm/container.h>
|
||||
#include "catch2/catch.hpp"
|
||||
|
||||
// Need these for the introspection of Synth
|
||||
#include "sfizz/PolyphonyGroup.h"
|
||||
#include "sfizz/RegionSet.h"
|
||||
|
||||
using namespace Catch::literals;
|
||||
using namespace sfz::literals;
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,9 @@
|
|||
using namespace Catch::literals;
|
||||
using namespace sfz::literals;
|
||||
|
||||
// Need these for the introspection of Synth
|
||||
#include "sfizz/Effects.h"
|
||||
|
||||
constexpr int blockSize { 256 };
|
||||
|
||||
TEST_CASE("[Synth] Play and check active voices")
|
||||
|
|
@ -1357,17 +1360,17 @@ TEST_CASE("[Synth] Initial values of CC")
|
|||
<region> sample=*sine
|
||||
)");
|
||||
|
||||
REQUIRE(synth.getHdccInit(111) == 0.0f);
|
||||
REQUIRE(synth.getHdccInit(7) == Approx(100.0f / 127)); // default volume
|
||||
REQUIRE(synth.getHdccInit(10) == 0.5f); // default pan
|
||||
REQUIRE(synth.getHdcc(111) == 0.0f);
|
||||
REQUIRE(synth.getHdcc(7) == Approx(100.0f / 127)); // default volume
|
||||
REQUIRE(synth.getHdcc(10) == 0.5f); // default pan
|
||||
|
||||
synth.loadSfzString(fs::current_path() / "init_cc.sfz", R"(
|
||||
<control> set_hdcc111=0.1234 set_cc112=77
|
||||
<region> sample=*sine
|
||||
)");
|
||||
|
||||
REQUIRE(synth.getHdccInit(111) == Approx(0.1234f));
|
||||
REQUIRE(synth.getHdccInit(112) == Approx(77.0f / 127));
|
||||
REQUIRE(synth.getHdcc(111) == Approx(0.1234f));
|
||||
REQUIRE(synth.getHdcc(112) == Approx(77.0f / 127));
|
||||
}
|
||||
|
||||
TEST_CASE("[Synth] Default ampeg_release")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue