Refactor of Synth and Voice into a pimpl pattern

This commit is contained in:
Paul Ferrand 2020-10-30 11:05:36 +01:00
parent b646cfc8a1
commit 3214211a06
13 changed files with 1955 additions and 1658 deletions

File diff suppressed because it is too large Load diff

View file

@ -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);
};

File diff suppressed because it is too large Load diff

View file

@ -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
View 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

View file

@ -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;

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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);

View file

@ -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

View file

@ -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;

View file

@ -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")