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
|
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "Resources.h"
|
|
||||||
#include "Parser.h"
|
|
||||||
#include "Voice.h"
|
|
||||||
#include "Region.h"
|
#include "Region.h"
|
||||||
#include "RegionSet.h"
|
#include "Voice.h"
|
||||||
#include "PolyphonyGroup.h"
|
|
||||||
#include "Effects.h"
|
|
||||||
#include "LeakDetector.h"
|
#include "LeakDetector.h"
|
||||||
#include "MidiState.h"
|
|
||||||
#include "AudioSpan.h"
|
#include "AudioSpan.h"
|
||||||
#include "parser/Parser.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 <absl/strings/string_view.h>
|
||||||
#include <random>
|
#include <memory>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace sfz {
|
namespace sfz {
|
||||||
class ControllerSource;
|
|
||||||
class LFOSource;
|
// Forward declarations for the introspection methods
|
||||||
class FlexEnvelopeSource;
|
class RegionSet;
|
||||||
class ADSREnvelopeSource;
|
class PolyphonyGroup;
|
||||||
|
class EffectBus;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief This class is the core of the sfizz library. In C++ it is the main point
|
* @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
|
* The jack_client.cpp file contains examples of the most classical usage of the
|
||||||
* synth and can be used as a reference.
|
* synth and can be used as a reference.
|
||||||
*/
|
*/
|
||||||
class Synth final : public Voice::StateListener, public Parser::Listener {
|
class Synth final {
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* @brief Construct a new Synth object with no voices. If you want sound
|
* @brief Construct a new Synth object with a default number of voices.
|
||||||
* you will need to call setNumVoices() before playing.
|
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
Synth();
|
Synth();
|
||||||
|
|
@ -80,13 +70,10 @@ public:
|
||||||
* @brief Destructor
|
* @brief Destructor
|
||||||
*/
|
*/
|
||||||
~Synth();
|
~Synth();
|
||||||
/**
|
Synth(const Synth& other) = delete;
|
||||||
* @brief Construct a new Synth object with a specified number of voices.
|
Synth& operator=(const Synth& other) = delete;
|
||||||
*
|
Synth(Synth&& other) = delete;
|
||||||
* @param numVoices
|
Synth& operator=(Synth&& other) = delete;
|
||||||
*/
|
|
||||||
Synth(int numVoices);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Processing mode
|
* @brief Processing mode
|
||||||
*/
|
*/
|
||||||
|
|
@ -122,11 +109,6 @@ public:
|
||||||
* @true otherwise.
|
* @true otherwise.
|
||||||
*/
|
*/
|
||||||
bool loadSfzString(const fs::path& path, absl::string_view text);
|
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.
|
* @brief Sets the tuning from a Scala file loaded from the file system.
|
||||||
*
|
*
|
||||||
|
|
@ -208,24 +190,6 @@ public:
|
||||||
* @return const Region*
|
* @return const Region*
|
||||||
*/
|
*/
|
||||||
const Region* getRegionById(NumericId<Region> id) const noexcept;
|
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
|
* @brief Get a raw view into a specific region. This is mostly used
|
||||||
* for testing.
|
* for testing.
|
||||||
|
|
@ -245,6 +209,7 @@ public:
|
||||||
/**
|
/**
|
||||||
* @brief Get a raw view into a specific effect bus. This is mostly used
|
* @brief Get a raw view into a specific effect bus. This is mostly used
|
||||||
* for testing.
|
* for testing.
|
||||||
|
* You'll need to include "Effects.h" to resolve the forward declaration.
|
||||||
*
|
*
|
||||||
* @param idx
|
* @param idx
|
||||||
* @return const EffectBus*
|
* @return const EffectBus*
|
||||||
|
|
@ -253,6 +218,7 @@ public:
|
||||||
/**
|
/**
|
||||||
* @brief Get a raw view into a specific set of regions. This is mostly used
|
* @brief Get a raw view into a specific set of regions. This is mostly used
|
||||||
* for testing.
|
* for testing.
|
||||||
|
* You'll need to include "RegionSet.h" to resolve the forward declaration.
|
||||||
*
|
*
|
||||||
* @param idx
|
* @param idx
|
||||||
* @return const RegionSet*
|
* @return const RegionSet*
|
||||||
|
|
@ -261,6 +227,7 @@ public:
|
||||||
/**
|
/**
|
||||||
* @brief Get a raw view into a specific polyphony group. This is mostly used
|
* @brief Get a raw view into a specific polyphony group. This is mostly used
|
||||||
* for testing.
|
* for testing.
|
||||||
|
* You'll need to include "PolyphonyGroup.h" to resolve the forward declaration.
|
||||||
*
|
*
|
||||||
* @param idx
|
* @param idx
|
||||||
* @return const PolyphonyGroup*
|
* @return const PolyphonyGroup*
|
||||||
|
|
@ -370,29 +337,13 @@ public:
|
||||||
* @param normValue the normalized cc value, in domain 0 to 1
|
* @param normValue the normalized cc value, in domain 0 to 1
|
||||||
*/
|
*/
|
||||||
void hdcc(int delay, int ccNumber, float normValue) noexcept;
|
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 ccNumber the cc number
|
||||||
* @param ccValue the cc value
|
* @return the current value
|
||||||
*/
|
*/
|
||||||
void initCc(int ccNumber, uint8_t ccValue) noexcept;
|
float getHdcc(int ccNumber);
|
||||||
/**
|
|
||||||
* @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);
|
|
||||||
/**
|
/**
|
||||||
* @brief Send a pitch bend event to the synth
|
* @brief Send a pitch bend event to the synth
|
||||||
*
|
*
|
||||||
|
|
@ -475,15 +426,6 @@ public:
|
||||||
* @param numVoices
|
* @param numVoices
|
||||||
*/
|
*/
|
||||||
void setNumVoices(int numVoices) noexcept;
|
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.
|
* @brief Set the oversampling factor to a new value.
|
||||||
|
|
@ -551,8 +493,8 @@ public:
|
||||||
*/
|
*/
|
||||||
void disableFreeWheeling() noexcept;
|
void disableFreeWheeling() noexcept;
|
||||||
|
|
||||||
Resources& getResources() noexcept { return resources; }
|
Resources& getResources() noexcept;
|
||||||
const Resources& getResources() const noexcept { return resources; }
|
const Resources& getResources() const noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Check if the SFZ should be reloaded.
|
* @brief Check if the SFZ should be reloaded.
|
||||||
|
|
@ -604,26 +546,26 @@ public:
|
||||||
*
|
*
|
||||||
* @return A reference to the parser.
|
* @return A reference to the parser.
|
||||||
*/
|
*/
|
||||||
Parser& getParser() noexcept { return parser; }
|
Parser& getParser() noexcept;
|
||||||
/**
|
/**
|
||||||
* @brief Get the parser.
|
* @brief Get the parser.
|
||||||
*
|
*
|
||||||
* @return A reference to 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
|
* @brief Get the key labels, if any
|
||||||
*
|
*
|
||||||
* @return const std::vector<NoteNamePair>&
|
* @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
|
* @brief Get the CC labels, if any
|
||||||
*
|
*
|
||||||
* @return const std::vector<NoteNamePair>&
|
* @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
|
* @brief Get the used CCs
|
||||||
|
|
@ -632,332 +574,9 @@ public:
|
||||||
*/
|
*/
|
||||||
std::bitset<config::numCCs> getUsedCCs() const noexcept;
|
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:
|
private:
|
||||||
/**
|
struct Impl;
|
||||||
* @brief change the group maximum polyphony
|
std::unique_ptr<Impl> impl_;
|
||||||
*
|
|
||||||
* @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 { };
|
|
||||||
|
|
||||||
LEAK_DETECTOR(Synth);
|
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
|
// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "TriggerEvent.h"
|
|
||||||
#include "Config.h"
|
|
||||||
#include "ADSREnvelope.h"
|
#include "ADSREnvelope.h"
|
||||||
#include "HistoricalBuffer.h"
|
#include "TriggerEvent.h"
|
||||||
#include "Region.h"
|
#include "Region.h"
|
||||||
#include "AudioBuffer.h"
|
|
||||||
#include "Resources.h"
|
#include "Resources.h"
|
||||||
#include "FilterPool.h"
|
|
||||||
#include "EQPool.h"
|
|
||||||
#include "Smoothers.h"
|
|
||||||
#include "AudioSpan.h"
|
#include "AudioSpan.h"
|
||||||
#include "LeakDetector.h"
|
#include "LeakDetector.h"
|
||||||
#include "OnePoleFilter.h"
|
|
||||||
#include "PowerFollower.h"
|
|
||||||
#include "utility/NumericId.h"
|
#include "utility/NumericId.h"
|
||||||
#include "absl/types/span.h"
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <random>
|
|
||||||
|
|
||||||
namespace sfz {
|
namespace sfz {
|
||||||
enum InterpolatorModel : int;
|
enum InterpolatorModel : int;
|
||||||
|
|
@ -44,16 +34,16 @@ public:
|
||||||
* @param midiState
|
* @param midiState
|
||||||
*/
|
*/
|
||||||
Voice(int voiceNumber, Resources& resources);
|
Voice(int voiceNumber, Resources& resources);
|
||||||
|
|
||||||
~Voice();
|
~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
|
* @brief Get the unique identifier of this voice in a synth
|
||||||
*/
|
*/
|
||||||
NumericId<Voice> getId() const noexcept
|
NumericId<Voice> getId() const noexcept;
|
||||||
{
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class State {
|
enum class State {
|
||||||
idle,
|
idle,
|
||||||
|
|
@ -69,12 +59,12 @@ public:
|
||||||
/**
|
/**
|
||||||
* @brief Return true if the voice is to be cleaned up (zombie state)
|
* @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.
|
* @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
|
* @brief Change the sample rate of the voice. This is used to compute all
|
||||||
|
|
@ -98,13 +88,13 @@ public:
|
||||||
*
|
*
|
||||||
* @return float
|
* @return float
|
||||||
*/
|
*/
|
||||||
float getSampleRate() const noexcept { return sampleRate; }
|
float getSampleRate() const noexcept;
|
||||||
/**
|
/**
|
||||||
* @brief Get the expected block size.
|
* @brief Get the expected block size.
|
||||||
*
|
*
|
||||||
* @return int
|
* @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)
|
* @brief Start playing a region after a short delay for different triggers (note on, off, cc)
|
||||||
|
|
@ -199,7 +189,7 @@ public:
|
||||||
*
|
*
|
||||||
* @return int
|
* @return int
|
||||||
*/
|
*/
|
||||||
const TriggerEvent& getTriggerEvent() const noexcept { return triggerEvent; }
|
const TriggerEvent& getTriggerEvent() const noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Reset the voice to its initial values
|
* @brief Reset the voice to its initial values
|
||||||
|
|
@ -232,14 +222,14 @@ public:
|
||||||
*
|
*
|
||||||
* @return Voice*
|
* @return Voice*
|
||||||
*/
|
*/
|
||||||
Voice* getNextSisterVoice() const noexcept { return nextSisterVoice; };
|
Voice* getNextSisterVoice() const noexcept { return nextSisterVoice_; };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get the previous sister voice in the ring
|
* @brief Get the previous sister voice in the ring
|
||||||
*
|
*
|
||||||
* @return Voice*
|
* @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
|
* @brief Get the mean squared power of the last rendered block. This is used
|
||||||
|
|
@ -266,19 +256,19 @@ public:
|
||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
const Region* getRegion() const noexcept { return region; }
|
const Region* getRegion() const noexcept;
|
||||||
/**
|
/**
|
||||||
* @brief Get the LFO designated by the given index
|
* @brief Get the LFO designated by the given index
|
||||||
*
|
*
|
||||||
* @param 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
|
* @brief Get the Flex EG designated by the given index
|
||||||
*
|
*
|
||||||
* @param 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
|
* @brief Set the max number of filters per voice
|
||||||
*
|
*
|
||||||
|
|
@ -337,250 +327,42 @@ public:
|
||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
int getAge() const noexcept { return age; }
|
int getAge() const noexcept;
|
||||||
|
|
||||||
Duration getLastDataDuration() const noexcept { return dataDuration; }
|
Duration getLastDataDuration() const noexcept;
|
||||||
Duration getLastAmplitudeDuration() const noexcept { return amplitudeDuration; }
|
Duration getLastAmplitudeDuration() const noexcept;
|
||||||
Duration getLastFilterDuration() const noexcept { return filterDuration; }
|
Duration getLastFilterDuration() const noexcept;
|
||||||
Duration getLastPanningDuration() const noexcept { return panningDuration; }
|
Duration getLastPanningDuration() const noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get the SFZv1 amplitude EG, if existing
|
* @brief Get the SFZv1 amplitude EG, if existing
|
||||||
*/
|
*/
|
||||||
ADSREnvelope<float>* getAmplitudeEG() { return &egAmplitude; }
|
ADSREnvelope<float>* getAmplitudeEG();
|
||||||
/**
|
/**
|
||||||
* @brief Get the SFZv1 pitch EG, if existing
|
* @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
|
* @brief Get the SFZv1 filter EG, if existing
|
||||||
*/
|
*/
|
||||||
ADSREnvelope<float>* getFilterEG() { return egFilter.get(); }
|
ADSREnvelope<float>* getFilterEG();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get the trigger event
|
* @brief Get the trigger event
|
||||||
*/
|
*/
|
||||||
const TriggerEvent& getTriggerEvent() { return triggerEvent; }
|
const TriggerEvent& getTriggerEvent();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
struct Impl;
|
||||||
* @brief Fill a span with data from a file source. This is the first step
|
std::unique_ptr<Impl> impl_;
|
||||||
* 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;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Remove the voice from the sister ring
|
* @brief Remove the voice from the sister ring
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
void removeVoiceFromRing() noexcept;
|
void removeVoiceFromRing() noexcept;
|
||||||
|
Voice* nextSisterVoice_ { this };
|
||||||
/**
|
Voice* previousSisterVoice_ { this };
|
||||||
* @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;
|
|
||||||
|
|
||||||
LEAK_DETECTOR(Voice);
|
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 {
|
namespace sfz {
|
||||||
|
|
||||||
ADSREnvelopeSource::ADSREnvelopeSource(Synth &synth)
|
ADSREnvelopeSource::ADSREnvelopeSource(VoiceList& list, MidiState& state)
|
||||||
: synth_(&synth)
|
: voiceList_(list), midiState_(state)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void ADSREnvelopeSource::init(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay)
|
void ADSREnvelopeSource::init(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay)
|
||||||
{
|
{
|
||||||
Synth& synth = *synth_;
|
Voice* voice = voiceList_.getVoiceById(voiceId);
|
||||||
|
|
||||||
Voice* voice = synth.getVoiceById(voiceId);
|
|
||||||
if (!voice) {
|
if (!voice) {
|
||||||
ASSERTFALSE;
|
ASSERTFALSE;
|
||||||
return;
|
return;
|
||||||
|
|
@ -55,17 +53,14 @@ void ADSREnvelopeSource::init(const ModKey& sourceKey, NumericId<Voice> voiceId,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Resources& resources = synth.getResources();
|
|
||||||
const TriggerEvent& triggerEvent = voice->getTriggerEvent();
|
const TriggerEvent& triggerEvent = voice->getTriggerEvent();
|
||||||
const float sampleRate = voice->getSampleRate();
|
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)
|
void ADSREnvelopeSource::release(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay)
|
||||||
{
|
{
|
||||||
Synth& synth = *synth_;
|
Voice* voice = voiceList_.getVoiceById(voiceId);
|
||||||
|
|
||||||
Voice* voice = synth.getVoiceById(voiceId);
|
|
||||||
if (!voice) {
|
if (!voice) {
|
||||||
ASSERTFALSE;
|
ASSERTFALSE;
|
||||||
return;
|
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)
|
void ADSREnvelopeSource::generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl::Span<float> buffer)
|
||||||
{
|
{
|
||||||
Synth& synth = *synth_;
|
Voice* voice = voiceList_.getVoiceById(voiceId);
|
||||||
|
|
||||||
Voice* voice = synth.getVoiceById(voiceId);
|
|
||||||
if (!voice) {
|
if (!voice) {
|
||||||
ASSERTFALSE;
|
ASSERTFALSE;
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -6,19 +6,22 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "../ModGenerator.h"
|
#include "../ModGenerator.h"
|
||||||
|
#include "../../VoiceList.h"
|
||||||
|
#include "../../MidiState.h"
|
||||||
|
|
||||||
namespace sfz {
|
namespace sfz {
|
||||||
class Synth;
|
class Synth;
|
||||||
|
|
||||||
class ADSREnvelopeSource : public ModGenerator {
|
class ADSREnvelopeSource : public ModGenerator {
|
||||||
public:
|
public:
|
||||||
explicit ADSREnvelopeSource(Synth &synth);
|
explicit ADSREnvelopeSource(VoiceList &synth, MidiState& state);
|
||||||
void init(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override;
|
void init(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override;
|
||||||
void release(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;
|
void generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl::Span<float> buffer) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Synth* synth_ = nullptr;
|
VoiceList& voiceList_;
|
||||||
|
MidiState& midiState_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace sfz
|
} // namespace sfz
|
||||||
|
|
|
||||||
|
|
@ -14,17 +14,16 @@
|
||||||
|
|
||||||
namespace sfz {
|
namespace sfz {
|
||||||
|
|
||||||
FlexEnvelopeSource::FlexEnvelopeSource(Synth &synth)
|
FlexEnvelopeSource::FlexEnvelopeSource(VoiceList& list)
|
||||||
: synth_(&synth)
|
: voiceList_(list)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void FlexEnvelopeSource::init(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay)
|
void FlexEnvelopeSource::init(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay)
|
||||||
{
|
{
|
||||||
Synth& synth = *synth_;
|
|
||||||
unsigned egIndex = sourceKey.parameters().N;
|
unsigned egIndex = sourceKey.parameters().N;
|
||||||
|
|
||||||
Voice* voice = synth.getVoiceById(voiceId);
|
Voice* voice = voiceList_.getVoiceById(voiceId);
|
||||||
if (!voice) {
|
if (!voice) {
|
||||||
ASSERTFALSE;
|
ASSERTFALSE;
|
||||||
return;
|
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)
|
void FlexEnvelopeSource::release(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay)
|
||||||
{
|
{
|
||||||
Synth& synth = *synth_;
|
|
||||||
unsigned egIndex = sourceKey.parameters().N;
|
unsigned egIndex = sourceKey.parameters().N;
|
||||||
|
|
||||||
Voice* voice = synth.getVoiceById(voiceId);
|
Voice* voice = voiceList_.getVoiceById(voiceId);
|
||||||
if (!voice) {
|
if (!voice) {
|
||||||
ASSERTFALSE;
|
ASSERTFALSE;
|
||||||
return;
|
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)
|
void FlexEnvelopeSource::generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl::Span<float> buffer)
|
||||||
{
|
{
|
||||||
Synth& synth = *synth_;
|
|
||||||
unsigned egIndex = sourceKey.parameters().N;
|
unsigned egIndex = sourceKey.parameters().N;
|
||||||
|
|
||||||
Voice* voice = synth.getVoiceById(voiceId);
|
Voice* voice = voiceList_.getVoiceById(voiceId);
|
||||||
if (!voice) {
|
if (!voice) {
|
||||||
ASSERTFALSE;
|
ASSERTFALSE;
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -6,19 +6,20 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "../ModGenerator.h"
|
#include "../ModGenerator.h"
|
||||||
|
#include "../../VoiceList.h"
|
||||||
|
|
||||||
namespace sfz {
|
namespace sfz {
|
||||||
class Synth;
|
class Synth;
|
||||||
|
|
||||||
class FlexEnvelopeSource : public ModGenerator {
|
class FlexEnvelopeSource : public ModGenerator {
|
||||||
public:
|
public:
|
||||||
explicit FlexEnvelopeSource(Synth &synth);
|
explicit FlexEnvelopeSource(VoiceList& list);
|
||||||
void init(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override;
|
void init(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override;
|
||||||
void release(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;
|
void generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl::Span<float> buffer) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Synth* synth_ = nullptr;
|
VoiceList& voiceList_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace sfz
|
} // namespace sfz
|
||||||
|
|
|
||||||
|
|
@ -14,17 +14,16 @@
|
||||||
|
|
||||||
namespace sfz {
|
namespace sfz {
|
||||||
|
|
||||||
LFOSource::LFOSource(Synth &synth)
|
LFOSource::LFOSource(VoiceList& list)
|
||||||
: synth_(&synth)
|
: voiceList_(list)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void LFOSource::init(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay)
|
void LFOSource::init(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay)
|
||||||
{
|
{
|
||||||
Synth& synth = *synth_;
|
|
||||||
unsigned lfoIndex = sourceKey.parameters().N;
|
unsigned lfoIndex = sourceKey.parameters().N;
|
||||||
|
|
||||||
Voice* voice = synth.getVoiceById(voiceId);
|
Voice* voice = voiceList_.getVoiceById(voiceId);
|
||||||
if (!voice) {
|
if (!voice) {
|
||||||
ASSERTFALSE;
|
ASSERTFALSE;
|
||||||
return;
|
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)
|
void LFOSource::generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl::Span<float> buffer)
|
||||||
{
|
{
|
||||||
Synth& synth = *synth_;
|
|
||||||
const unsigned lfoIndex = sourceKey.parameters().N;
|
const unsigned lfoIndex = sourceKey.parameters().N;
|
||||||
|
|
||||||
Voice* voice = synth.getVoiceById(voiceId);
|
Voice* voice = voiceList_.getVoiceById(voiceId);
|
||||||
if (!voice) {
|
if (!voice) {
|
||||||
ASSERTFALSE;
|
ASSERTFALSE;
|
||||||
fill(buffer, 0.0f);
|
fill(buffer, 0.0f);
|
||||||
|
|
|
||||||
|
|
@ -6,18 +6,18 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "../ModGenerator.h"
|
#include "../ModGenerator.h"
|
||||||
|
#include "../../VoiceList.h"
|
||||||
namespace sfz {
|
namespace sfz {
|
||||||
class Synth;
|
class Synth;
|
||||||
|
|
||||||
class LFOSource : public ModGenerator {
|
class LFOSource : public ModGenerator {
|
||||||
public:
|
public:
|
||||||
explicit LFOSource(Synth &synth);
|
explicit LFOSource(VoiceList &list);
|
||||||
void init(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override;
|
void init(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override;
|
||||||
void generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl::Span<float> buffer) override;
|
void generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl::Span<float> buffer) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Synth* synth_ = nullptr;
|
VoiceList& voiceList_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace sfz
|
} // namespace sfz
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,10 @@
|
||||||
#include <absl/algorithm/container.h>
|
#include <absl/algorithm/container.h>
|
||||||
#include "catch2/catch.hpp"
|
#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 Catch::literals;
|
||||||
using namespace sfz::literals;
|
using namespace sfz::literals;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,9 @@
|
||||||
using namespace Catch::literals;
|
using namespace Catch::literals;
|
||||||
using namespace sfz::literals;
|
using namespace sfz::literals;
|
||||||
|
|
||||||
|
// Need these for the introspection of Synth
|
||||||
|
#include "sfizz/Effects.h"
|
||||||
|
|
||||||
constexpr int blockSize { 256 };
|
constexpr int blockSize { 256 };
|
||||||
|
|
||||||
TEST_CASE("[Synth] Play and check active voices")
|
TEST_CASE("[Synth] Play and check active voices")
|
||||||
|
|
@ -1357,17 +1360,17 @@ TEST_CASE("[Synth] Initial values of CC")
|
||||||
<region> sample=*sine
|
<region> sample=*sine
|
||||||
)");
|
)");
|
||||||
|
|
||||||
REQUIRE(synth.getHdccInit(111) == 0.0f);
|
REQUIRE(synth.getHdcc(111) == 0.0f);
|
||||||
REQUIRE(synth.getHdccInit(7) == Approx(100.0f / 127)); // default volume
|
REQUIRE(synth.getHdcc(7) == Approx(100.0f / 127)); // default volume
|
||||||
REQUIRE(synth.getHdccInit(10) == 0.5f); // default pan
|
REQUIRE(synth.getHdcc(10) == 0.5f); // default pan
|
||||||
|
|
||||||
synth.loadSfzString(fs::current_path() / "init_cc.sfz", R"(
|
synth.loadSfzString(fs::current_path() / "init_cc.sfz", R"(
|
||||||
<control> set_hdcc111=0.1234 set_cc112=77
|
<control> set_hdcc111=0.1234 set_cc112=77
|
||||||
<region> sample=*sine
|
<region> sample=*sine
|
||||||
)");
|
)");
|
||||||
|
|
||||||
REQUIRE(synth.getHdccInit(111) == Approx(0.1234f));
|
REQUIRE(synth.getHdcc(111) == Approx(0.1234f));
|
||||||
REQUIRE(synth.getHdccInit(112) == Approx(77.0f / 127));
|
REQUIRE(synth.getHdcc(112) == Approx(77.0f / 127));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("[Synth] Default ampeg_release")
|
TEST_CASE("[Synth] Default ampeg_release")
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue