From aef8352cfc0468e025add03a7e51ff8cf2dab0d7 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Thu, 23 Jan 2020 22:17:06 +0100 Subject: [PATCH] Added a C++ opaque API for the shared lib --- clients/jack_client.cpp | 2 +- src/CMakeLists.txt | 4 +- src/sfizz.hpp | 251 +++++++++++++++++++++++++++++++++++++++- src/sfizz/Synth.cpp | 4 +- src/sfizz/Synth.h | 4 +- src/sfizz/sfizz.cpp | 180 ++++++++++++++++++++++++++++ src/sfizz_parser.hpp | 2 - tests/SynthT.cpp | 2 +- 8 files changed, 437 insertions(+), 12 deletions(-) create mode 100644 src/sfizz/sfizz.cpp delete mode 100644 src/sfizz_parser.hpp diff --git a/clients/jack_client.cpp b/clients/jack_client.cpp index 7791f071..f21bb873 100644 --- a/clients/jack_client.cpp +++ b/clients/jack_client.cpp @@ -21,7 +21,7 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -#include "sfizz.hpp" +#include "sfizz/Synth.h" #include #include #include diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7401add3..5d4fa59f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -37,12 +37,12 @@ add_library (sfizz::sfizz ALIAS sfizz_static) # Shared library and installation target if (SFIZZ_SHARED) add_library (sfizz_shared SHARED) - target_sources(sfizz_shared PRIVATE ${SFIZZ_SOURCES} sfizz/sfizz_wrapper.cpp) + target_sources(sfizz_shared PRIVATE ${SFIZZ_SOURCES} sfizz/sfizz_wrapper.cpp sfizz/sfizz.cpp) target_include_directories (sfizz_shared PRIVATE .) target_include_directories (sfizz_static PRIVATE external) target_link_libraries (sfizz_shared PRIVATE absl::strings absl::span sfizz_parser absl::flat_hash_map Threads::Threads) target_compile_definitions(sfizz_shared PRIVATE SFIZZ_EXPORT_SYMBOLS) - set_target_properties (sfizz_shared PROPERTIES OUTPUT_NAME sfizz PUBLIC_HEADER sfizz.h) + set_target_properties (sfizz_shared PROPERTIES OUTPUT_NAME sfizz PUBLIC_HEADER "sfizz.h;sfizz.hpp") sfizz_link_libsndfile(sfizz_shared) sfizz_enable_lto_if_needed(sfizz_shared) configure_file (${PROJECT_SOURCE_DIR}/scripts/sfizz.pc.in sfizz.pc @ONLY) diff --git a/src/sfizz.hpp b/src/sfizz.hpp index 4dd6ab25..1ac50852 100644 --- a/src/sfizz.hpp +++ b/src/sfizz.hpp @@ -1,2 +1,249 @@ -#include "sfizz/AudioSpan.h" -#include "sfizz/Synth.h" \ No newline at end of file +#include +#include +#include + +#if defined SFIZZ_EXPORT_SYMBOLS + #if defined _WIN32 + #define SFIZZ_EXPORTED_API __declspec(dllexport) + #else + #define SFIZZ_EXPORTED_API __attribute__ ((visibility ("default"))) + #endif +#else + #define SFIZZ_EXPORTED_API +#endif + +namespace sfz +{ +class Synth; +class SFIZZ_EXPORTED_API Sfizz +{ +public: + Sfizz(); + ~Sfizz(); + /** + * @brief Empties the current regions and load a new SFZ file into the synth. + * + * This function will disable all callbacks so it is safe to call from a + * UI thread for example, although it may generate a click. However it is + * not reentrant, so you should not call it from concurrent threads. + * + * @param file + * @return true + * @return false if the file was not found or no regions were loaded. + */ + bool loadSfzFile(const std::string& path); + /** + * @brief Get the current number of regions loaded + * + * @return int + */ + int getNumRegions() const noexcept; + /** + * @brief Get the current number of groups loaded + * + * @return int + */ + int getNumGroups() const noexcept; + /** + * @brief Get the current number of masters loaded + * + * @return int + */ + int getNumMasters() const noexcept; + /** + * @brief Get the current number of curves loaded + * + * @return int + */ + int getNumCurves() const noexcept; + const std::vector& getUnknownOpcodes() const noexcept; + /** + * @brief Get the number of preloaded samples in the synth + * + * @return size_t + */ + size_t getNumPreloadedSamples() const noexcept; + /** + * @brief Set the maximum size of the blocks for the callback. The actual + * size can be lower in each callback but should not be larger + * than this value. + * + * @param samplesPerBlock + */ + void setSamplesPerBlock(int samplesPerBlock) noexcept; + /** + * @brief Set the sample rate. If you do not call it it is initialized + * to sfz::config::defaultSampleRate. + * + * @param sampleRate + */ + void setSampleRate(float sampleRate) noexcept; + /** + * @brief Get the current value for the volume, in dB. + * + * @return float + */ + float getVolume() const noexcept; + /** + * @brief Set the value for the volume. This value will be + * clamped within sfz::default::volumeRange. + * + * @param volume + */ + void setVolume(float volume) noexcept; + + /** + * @brief Send a note on event to the synth + * + * @param delay the delay at which the event occurs; this should be lower + * than the size of the block in the next call to renderBlock(). + * @param noteNumber the midi note number + * @param velocity the midi note velocity + */ + void noteOn(int delay, int noteNumber, uint8_t velocity) noexcept; + /** + * @brief Send a note off event to the synth + * + * @param delay the delay at which the event occurs; this should be lower + * than the size of the block in the next call to renderBlock(). + * @param noteNumber the midi note number + * @param velocity the midi note velocity + */ + void noteOff(int delay, int noteNumber, uint8_t velocity) noexcept; + /** + * @brief Send a CC event to the synth + * + * @param delay the delay at which the event occurs; this should be lower than the size of + * the block in the next call to renderBlock(). + * @param ccNumber the cc number + * @param ccValue the cc value + */ + void cc(int delay, int ccNumber, uint8_t ccValue) noexcept; + /** + * @brief Send a pitch bend event to the synth + * + * @param delay the delay at which the event occurs; this should be lower + * than the size of the block in the next call to + * renderBlock(). + * @param pitch the pitch value centered between -8192 and 8192 + */ + void pitchWheel(int delay, int pitch) noexcept; + /** + * @brief Send a aftertouch event to the synth + * + * @param delay the delay at which the event occurs; this should be lower than the size of + * the block in the next call to renderBlock(). + * @param aftertouch the aftertouch value + */ + void aftertouch(int delay, uint8_t aftertouch) noexcept; + /** + * @brief Send a tempo event to the synth + * + * @param delay the delay at which the event occurs; this should be lower than the size of + * the block in the next call to renderBlock(). + * @param secondsPerQuarter the new period of the quarter note + */ + void tempo(int delay, float secondsPerQuarter) noexcept; + /** + * @brief Render an block of audio data in the buffer. This call will reset + * the synth in its waiting state for the next batch of events. The size of + * the block is integrated in the AudioSpan object. You can build an + * AudioSpan implicitely from a large number of source objects; check the + * AudioSpan reference for more precision. + * + * @param buffer the buffer to write the next block into + * @param numSamples the number of samples + */ + void renderBlock(float** buffers, size_t numSamples, int numOutputs) noexcept; + + /** + * @brief Get the number of active voices + * + * @return int + */ + int getNumActiveVoices() const noexcept; + /** + * @brief Get the total number of voices in the synth (the polyphony) + * + * @return int + */ + int getNumVoices() const noexcept; + /** + * @brief Change the number of voices (the polyphony) + * + * @param numVoices + */ + void setNumVoices(int numVoices) noexcept; + + /** + * @brief Set the oversampling factor to a new value. This will disable all callbacks + * kill all the voices, and trigger a reloading of every file in the FilePool under + * the new oversampling. + * + * @param factor + * @return true if the factor did indeed change + */ + bool setOversamplingFactor(int factor) noexcept; + + /** + * @brief get the current oversampling factor + * + * @return Oversampling + */ + int getOversamplingFactor() const noexcept; + + /** + * @brief Set the preloaded file size. This will disable the callback. + * + * @param factor + */ + void setPreloadSize(uint32_t preloadSize) noexcept; + + /** + * @brief get the current preloaded file size + * + * @return Oversampling + */ + uint32_t getPreloadSize() const noexcept; + + /** + * @brief Gets the number of allocated buffers. + * + * @return The allocated buffers. + */ + int getAllocatedBuffers() const noexcept; + + /** + * @brief Gets the number of bytes allocated through the buffers + * + * @return The allocated bytes. + */ + int getAllocatedBytes() const noexcept; + + /** + * @brief Enable freewheeling on the synth. This will wait for background + * loaded files to finish loading before each render callback to ensure that + * there will be no dropouts. + * + */ + void enableFreeWheeling() noexcept; + /** + * @brief Disable freewheeling on the synth. You should disable freewheeling + * before live use of the plugin otherwise the audio thread will lock. + * + */ + void disableFreeWheeling() noexcept; + /** + * @brief Check if the SFZ should be reloaded. + * + * Depending on the platform this can create file descriptors. + * + * @return true if any included files (including the root file) have + * been modified since the sfz file was loaded. + * @return false + */ + bool shouldReloadFile(); +private: + std::unique_ptr synth; +}; +} \ No newline at end of file diff --git a/src/sfizz/Synth.cpp b/src/sfizz/Synth.cpp index cf414774..1c8b7277 100644 --- a/src/sfizz/Synth.cpp +++ b/src/sfizz/Synth.cpp @@ -105,7 +105,7 @@ void sfz::Synth::buildRegion(const std::vector& regionOpcodes) } if (!lastRegion->parseOpcode(opcode)) - unknownOpcodes.insert(opcode.opcode); + unknownOpcodes.emplace_back(opcode.opcode); } }; @@ -556,7 +556,7 @@ const sfz::Voice* sfz::Synth::getVoiceView(int idx) const noexcept return (size_t)idx < voices.size() ? voices[idx].get() : nullptr; } -std::set sfz::Synth::getUnknownOpcodes() const noexcept +const std::vector& sfz::Synth::getUnknownOpcodes() const noexcept { return unknownOpcodes; } diff --git a/src/sfizz/Synth.h b/src/sfizz/Synth.h index 77abf1c1..5dea692c 100644 --- a/src/sfizz/Synth.h +++ b/src/sfizz/Synth.h @@ -154,7 +154,7 @@ public: * * @return std::set */ - std::set getUnknownOpcodes() const noexcept; + const std::vector& getUnknownOpcodes() const noexcept; /** * @brief Get the number of preloaded samples in the synth * @@ -432,7 +432,7 @@ private: std::vector ccNames; // Default active switch if multiple keyswitchable regions are present absl::optional defaultSwitch; - std::set unknownOpcodes; + std::vector unknownOpcodes; using RegionPtrVector = std::vector; using VoicePtrVector = std::vector; std::vector> regions; diff --git a/src/sfizz/sfizz.cpp b/src/sfizz/sfizz.cpp new file mode 100644 index 00000000..4fde9f9c --- /dev/null +++ b/src/sfizz/sfizz.cpp @@ -0,0 +1,180 @@ +#include "Synth.h" +#include "sfizz.hpp" + +sfz::Sfizz::Sfizz() +{ + synth = std::make_unique(); +} + +sfz::Sfizz::~Sfizz() +{ + +} + +bool sfz::Sfizz::loadSfzFile(const std::string& path) +{ + return synth->loadSfzFile(path); +} + +int sfz::Sfizz::getNumRegions() const noexcept +{ + return synth->getNumRegions(); +} + +int sfz::Sfizz::getNumGroups() const noexcept +{ + return synth->getNumGroups(); +} + +int sfz::Sfizz::getNumMasters() const noexcept +{ + return synth->getNumMasters(); +} + +int sfz::Sfizz::getNumCurves() const noexcept +{ + return synth->getNumCurves(); +} + +const std::vector& sfz::Sfizz::getUnknownOpcodes() const noexcept +{ + return synth->getUnknownOpcodes(); +} + +size_t sfz::Sfizz::getNumPreloadedSamples() const noexcept +{ + return synth->getNumPreloadedSamples(); +} + +void sfz::Sfizz::setSamplesPerBlock(int samplesPerBlock) noexcept +{ + synth->setSamplesPerBlock(samplesPerBlock); +} + +void sfz::Sfizz::setSampleRate(float sampleRate) noexcept +{ + synth->setSampleRate(sampleRate); +} + +float sfz::Sfizz::getVolume() const noexcept +{ + return synth->getVolume(); +} + +void sfz::Sfizz::setVolume(float volume) noexcept +{ + synth->setVolume(volume); +} + +void sfz::Sfizz::noteOn(int delay, int noteNumber, uint8_t velocity) noexcept +{ + synth->noteOn(delay, noteNumber, velocity); +} + +void sfz::Sfizz::noteOff(int delay, int noteNumber, uint8_t velocity) noexcept +{ + synth->noteOff(delay, noteNumber, velocity); +} + +void sfz::Sfizz::cc(int delay, int ccNumber, uint8_t ccValue) noexcept +{ + synth->cc(delay, ccNumber, ccValue); +} + +void sfz::Sfizz::pitchWheel(int delay, int pitch) noexcept +{ + synth->pitchWheel(delay, pitch); +} + +void sfz::Sfizz::aftertouch(int delay, uint8_t aftertouch) noexcept +{ + synth->aftertouch(delay, aftertouch); +} + +void sfz::Sfizz::tempo(int delay, float secondsPerQuarter) noexcept +{ + synth->tempo(delay, secondsPerQuarter); +} + +void sfz::Sfizz::renderBlock(float** buffers, size_t numSamples, int /*numOutputs*/) noexcept +{ + synth->renderBlock({{buffers[0], buffers[1]}, numSamples}); +} + +int sfz::Sfizz::getNumActiveVoices() const noexcept +{ + return synth->getNumActiveVoices(); +} + +int sfz::Sfizz::getNumVoices() const noexcept +{ + return synth->getNumVoices(); +} + +void sfz::Sfizz::setNumVoices(int numVoices) noexcept +{ + synth->setNumVoices(numVoices); +} + +bool sfz::Sfizz::setOversamplingFactor(int factor) noexcept +{ + using sfz::Oversampling; + switch(factor) + { + case 1: + synth->setOversamplingFactor(sfz::Oversampling::x1); + return true; + case 2: + synth->setOversamplingFactor(sfz::Oversampling::x2); + return true; + case 4: + synth->setOversamplingFactor(sfz::Oversampling::x4); + return true; + case 8: + synth->setOversamplingFactor(sfz::Oversampling::x8); + return true; + default: + return false; + } +} + + +int sfz::Sfizz::getOversamplingFactor() const noexcept +{ + return static_cast(synth->getOversamplingFactor()); +} + +void sfz::Sfizz::setPreloadSize(uint32_t preloadSize) noexcept +{ + synth->setPreloadSize(preloadSize); +} + +uint32_t sfz::Sfizz::getPreloadSize() const noexcept +{ + return synth->getPreloadSize(); +} + +int sfz::Sfizz::getAllocatedBuffers() const noexcept +{ + return synth->getAllocatedBuffers(); +} + +int sfz::Sfizz::getAllocatedBytes() const noexcept +{ + return synth->getAllocatedBytes(); +} + +void sfz::Sfizz::enableFreeWheeling() noexcept +{ + synth->enableFreeWheeling(); +} + +void sfz::Sfizz::disableFreeWheeling() noexcept +{ + synth->disableFreeWheeling(); +} + +bool sfz::Sfizz::shouldReloadFile() +{ + return synth->shouldReloadFile(); +} \ No newline at end of file diff --git a/src/sfizz_parser.hpp b/src/sfizz_parser.hpp deleted file mode 100644 index 49ef76bc..00000000 --- a/src/sfizz_parser.hpp +++ /dev/null @@ -1,2 +0,0 @@ -#include "sfizz/Parser.h" -#include "sfizz/Opcode.h" \ No newline at end of file diff --git a/tests/SynthT.cpp b/tests/SynthT.cpp index 1833c82d..8b030672 100644 --- a/tests/SynthT.cpp +++ b/tests/SynthT.cpp @@ -21,7 +21,7 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -#include "sfizz.hpp" +#include "sfizz/Synth.h" #include "catch2/catch.hpp" using namespace Catch::literals;