synthizer
Advanced tools
| /** | ||
| * Run through the wave types supported by the fast sine bank generator. | ||
| * */ | ||
| #include "example_common.h" | ||
| #include "synthizer.h" | ||
| #include "synthizer_constants.h" | ||
| #include <assert.h> | ||
| #include <chrono> | ||
| #include <math.h> | ||
| #include <stdio.h> | ||
| #include <thread> | ||
| void driveGenerator(syz_Handle g) { | ||
| for (double freq = 300.0; freq > 100.0; freq -= 5.0) { | ||
| CHECKED(syz_setD(g, SYZ_P_FREQUENCY, freq)); | ||
| std::this_thread::sleep_for(std::chrono::milliseconds(30)); | ||
| } | ||
| } | ||
| int main() { | ||
| syz_Handle context, generator, source; | ||
| CHECKED(syz_initialize()); | ||
| CHECKED(syz_createContext(&context, NULL, NULL)); | ||
| CHECKED(syz_createDirectSource(&source, context, NULL, NULL, NULL)); | ||
| printf("Sine\n"); | ||
| CHECKED(syz_createFastSineBankGeneratorSine(&generator, context, 300.0, NULL, NULL, NULL)); | ||
| CHECKED(syz_sourceAddGenerator(source, generator)); | ||
| driveGenerator(generator); | ||
| syz_handleDecRef(generator); | ||
| generator = 0; | ||
| printf("Square partials=10\n"); | ||
| CHECKED(syz_createFastSineBankGeneratorSquare(&generator, context, 300.0, 10, NULL, NULL, NULL)); | ||
| CHECKED(syz_sourceAddGenerator(source, generator)); | ||
| driveGenerator(generator); | ||
| syz_handleDecRef(generator); | ||
| generator = 0; | ||
| printf("triangle partials=10\n"); | ||
| CHECKED(syz_createFastSineBankGeneratorTriangle(&generator, context, 300.0, 10, NULL, NULL, NULL)); | ||
| CHECKED(syz_sourceAddGenerator(source, generator)); | ||
| driveGenerator(generator); | ||
| syz_handleDecRef(generator); | ||
| generator = 0; | ||
| printf("sawtooth partials=30\n"); | ||
| CHECKED(syz_createFastSineBankGeneratorSaw(&generator, context, 300.0, 30, NULL, NULL, NULL)); | ||
| CHECKED(syz_sourceAddGenerator(source, generator)); | ||
| driveGenerator(generator); | ||
| syz_handleDecRef(generator); | ||
| generator = 0; | ||
| syz_handleDecRef(source); | ||
| syz_handleDecRef(generator); | ||
| syz_handleDecRef(context); | ||
| syz_shutdown(); | ||
| return 0; | ||
| } |
| #pragma once | ||
| /** A sine bank based off the following identities: | ||
| * | ||
| * ``` | ||
| * sin(a + b) = sin(a) * cos(b) + cos(a) * sin(b); | ||
| * cos(a + b) = cos(a) * cos(b) - sin(a) * sin(b) | ||
| * ``` | ||
| * | ||
| * By holding b constant, we can do the entire thing without any actual trig functions after initialization. That is a | ||
| * is the advancing/output angle. | ||
| * | ||
| * This uses templates for autovectorization of computation, primarily (but not only) for multiples of 4. To keep | ||
| * accuracy, it resynchronizes each time `fillBlock` is called. | ||
| * | ||
| * SR is hardcoded at the Synthizer samplerate, as per usual. | ||
| * */ | ||
| #include <algorithm> | ||
| #include <math.h> // Older gcc doesn't like cmath because sinf and cosf don't exist. | ||
| #include <vector> | ||
| #include "synthizer/config.hpp" | ||
| #include "synthizer/math.hpp" | ||
| #include "synthizer/memory.hpp" | ||
| namespace synthizer { | ||
| /** | ||
| * Configuration for an individual sine wave. | ||
| * */ | ||
| class SineWaveConfig { | ||
| public: | ||
| SineWaveConfig(double _freq_mul, double _phase, double _gain) : phase(_phase), freq_mul(_freq_mul), gain(_gain) {} | ||
| /* Phase, in the range 0.0 to 1.0. */ | ||
| double phase; | ||
| /* Frequency, as a multiplier of the base frequency. E.g 1.0 is the base frequency, 2.0 the first harmonic. */ | ||
| double freq_mul; | ||
| double gain; | ||
| }; | ||
| class FastSineBank { | ||
| public: | ||
| FastSineBank(double _frequency); | ||
| void addWave(const SineWaveConfig &wave); | ||
| void clearWaves(); | ||
| void setFrequency(double frequency); | ||
| /* Fill a block of `SAMPLES` length with the output from this generator. */ | ||
| template <std::size_t SAMPLES, bool ADD = true> void fillBlock(float *out); | ||
| private: | ||
| template <std::size_t SAMPLES, std::size_t WAVES> void fillBlockHelper(float *out, std::size_t start); | ||
| deferred_vector<SineWaveConfig> waves; | ||
| double frequency; | ||
| // To allow for frequency changes without artifacts or fading, we must store time on a per-wave basis. | ||
| class WaveState { | ||
| public: | ||
| WaveState(double _time) : time(_time) {} | ||
| // Time for this wave specifically in the range 0.0 to 1.0. | ||
| double time = 0.0; | ||
| }; | ||
| std::vector<WaveState> wave_states; | ||
| }; | ||
| FastSineBank::FastSineBank(double _frequency) : frequency(_frequency) {} | ||
| template <std::size_t SAMPLES, std::size_t WAVES> void FastSineBank::fillBlockHelper(float *out, std::size_t start) { | ||
| /* sa=sin(a), ca=cos(a), etc. */ | ||
| float sa[WAVES], ca[WAVES], sb[WAVES], cb[WAVES], gains[WAVES]; | ||
| // Initialize our variables using real values. | ||
| for (std::size_t i = 0; i < WAVES; i++) { | ||
| std::size_t wave_ind = i + start; | ||
| double freq = this->waves[wave_ind].freq_mul * this->frequency; | ||
| double t = 2 * PI * this->wave_states[wave_ind].time; | ||
| // Advance the wave's time. | ||
| this->wave_states[wave_ind].time = | ||
| fmod(this->wave_states[wave_ind].time + freq * (SAMPLES / (double)config::SR), 1.0); | ||
| sa[i] = sinf(t); | ||
| ca[i] = cosf(t); | ||
| // How many radians does this wave advance by per sample? | ||
| double b = 2 * PI * freq / config::SR; | ||
| sb[i] = sinf(b); | ||
| cb[i] = cosf(b); | ||
| gains[i] = waves[wave_ind].gain; | ||
| } | ||
| for (std::size_t s = 0; s < SAMPLES; s++) { | ||
| for (std::size_t i = 0; i < WAVES; i++) { | ||
| float new_sa = sa[i] * cb[i] + ca[i] * sb[i]; | ||
| float new_ca = ca[i] * cb[i] - sa[i] * sb[i]; | ||
| out[s] += gains[i] * sa[i]; | ||
| sa[i] = new_sa; | ||
| ca[i] = new_ca; | ||
| } | ||
| } | ||
| } | ||
| template <std::size_t SAMPLES, bool ADD> void FastSineBank::fillBlock(float *out) { | ||
| if (ADD == false) { | ||
| std::fill(out, out + SAMPLES, 0.0); | ||
| } | ||
| // break this down by 16, 8, 4, 2, 1. Gives the compiler lots of autovectorization room. | ||
| std::size_t i = 0; | ||
| #define BLOCK(X) \ | ||
| for (; i < this->waves.size() / X * X; i += X) { \ | ||
| this->fillBlockHelper<SAMPLES, X>(out, i); \ | ||
| } | ||
| BLOCK(32) | ||
| BLOCK(16) | ||
| BLOCK(8) | ||
| BLOCK(4) | ||
| BLOCK(1) | ||
| #undef BLOCK | ||
| } | ||
| void FastSineBank::addWave(const SineWaveConfig &wave) { | ||
| this->waves.push_back(wave); | ||
| this->wave_states.emplace_back(FastSineBank::WaveState{wave.phase}); | ||
| } | ||
| void FastSineBank::clearWaves() { | ||
| this->waves.clear(); | ||
| this->wave_states.clear(); | ||
| } | ||
| void FastSineBank::setFrequency(double _frequency) { this->frequency = _frequency; } | ||
| } // namespace synthizer |
| #pragma once | ||
| #include "synthizer.h" | ||
| #include "synthizer/fast_sine_bank.hpp" | ||
| #include "synthizer/generator.hpp" | ||
| #include "synthizer/property_internals.hpp" | ||
| #include <memory> | ||
| #include <optional> | ||
| namespace synthizer { | ||
| class Context; | ||
| class FastSineBankGenerator : public Generator { | ||
| public: | ||
| FastSineBankGenerator(const std::shared_ptr<Context> &context, const syz_SineBankConfig *cfg); | ||
| int getObjectType() override; | ||
| unsigned int getChannels() override; | ||
| void generateBlock(float *output, FadeDriver *gain_driver) override; | ||
| std::optional<double> startGeneratorLingering() override; | ||
| #define PROPERTY_CLASS SineBankGenerator | ||
| #define PROPERTY_BASE Generator | ||
| #define PROPERTY_LIST SINE_BANK_GENERATOR_PROPERTIES | ||
| #include "synthizer/property_impl.hpp" | ||
| private: | ||
| FastSineBank bank; | ||
| }; | ||
| } // namespace synthizer |
| #include "synthizer/generators/fast_sine_bank.hpp" | ||
| #include "synthizer.h" | ||
| #include "synthizer/block_buffer_cache.hpp" | ||
| #include "synthizer/c_api.hpp" | ||
| #include "synthizer/config.hpp" | ||
| #include "synthizer/context.hpp" | ||
| #include "synthizer/math.hpp" | ||
| #include <math.h> | ||
| #include <memory> | ||
| #include <optional> | ||
| #include <vector> | ||
| namespace synthizer { | ||
| FastSineBankGenerator::FastSineBankGenerator(const std::shared_ptr<Context> &context, const syz_SineBankConfig *cfg) | ||
| : Generator(context), bank(cfg->initial_frequency) { | ||
| for (unsigned int i = 0; i < cfg->wave_count; i++) { | ||
| SineWaveConfig wave{cfg->waves[i].frequency_mul, cfg->waves[i].phase, cfg->waves[i].gain}; | ||
| this->bank.addWave(wave); | ||
| } | ||
| // Make sure the property subsystem starts at the right place. | ||
| this->setFrequency(cfg->initial_frequency, false); | ||
| } | ||
| int FastSineBankGenerator::getObjectType() { return SYZ_OTYPE_FAST_SINE_BANK_GENERATOR; } | ||
| unsigned int FastSineBankGenerator::getChannels() { return 1; } | ||
| // The weird parameter name gd is due to what is basically a false positive warning from MSVC, which doesn't like that | ||
| // the base class has a private member called gain_driver. | ||
| void FastSineBankGenerator::generateBlock(float *output, FadeDriver *gd) { | ||
| // For now, we round-trip through a temporary buffer unconditionally to apply the gain; in future, this restriction | ||
| // may be lifted due to improvements in the underlying fade architecture. Note that as is standard with Synthizer | ||
| // objects, the bank adds. | ||
| auto tmp_buffer = acquireBlockBuffer(true); | ||
| this->bank.setFrequency(this->getFrequency()); | ||
| this->bank.fillBlock<config::BLOCK_SIZE>(tmp_buffer); | ||
| gd->drive(this->getContextRaw()->getBlockTime(), [&](auto &&gain_cb) { | ||
| for (unsigned int i = 0; i < config::BLOCK_SIZE; i++) { | ||
| output[i] += tmp_buffer[i] * gain_cb(i); | ||
| } | ||
| }); | ||
| } | ||
| std::optional<double> FastSineBankGenerator::startGeneratorLingering() { | ||
| this->setGain(0.0); | ||
| return 2 * (config::BLOCK_SIZE / (double)config::SR); | ||
| } | ||
| /** | ||
| * Apply the Lanczos sigma approximation to some sine waves, assuming that they are in order and that the first wave is | ||
| * the fundamental. This function assumes that these are integral harmonics and that the vec is non-empty and in order | ||
| * of increasing frequency. | ||
| * | ||
| * This is a strategy to reduce the gibbs phenomenon when approximating fourier series. | ||
| * */ | ||
| static void sigmaApproximate(std::vector<syz_SineBankWave> *waves) { | ||
| // Per wolfram, rewrite the fourier series as: | ||
| // `f(theta)=1/2a_0+sum_(n=1)^(m-1)sinc((npi)/(2m))[a_ncos(ntheta)+b_nsin(ntheta)]` where `m` is the last term. | ||
| // | ||
| // Wolfram is unclear on the meaning of "last term" here: in the normal fourier series, the last term would not be | ||
| // included by the above sum. There is another definition which they claim is incorrect where the upper end of the | ||
| // sum is `m`, plus a few other variations. It probably doesn't matter much as long as we never evaluate the sinc | ||
| // function outside the interval `(0, pi/2]` (0 is a special case, and not included by any definition, and any value | ||
| // after `pi/2` is negative). As a consequence we take the interpretation that `m= h + 1` where h is the highest | ||
| // harmonic, and split the difference. At the end of the day, if this sounds good enough that's all we care about. | ||
| double m = waves->back().frequency_mul + 1; | ||
| // should always be exactly an integer. | ||
| assert((unsigned int)m == m); | ||
| // In practice, the above is actually a multiplication on the gain of each term, and we already have those gains. To | ||
| // do this, run over the array and multiply them in. | ||
| for (auto &w : *waves) { | ||
| double n = w.frequency_mul; | ||
| // Sinc function. We know the parameter can never be zero. | ||
| double sigma = sin(PI * n / (2 * m)); | ||
| w.gain *= sigma; | ||
| } | ||
| } | ||
| /** | ||
| * Normalize a set of waves. | ||
| * */ | ||
| static void normalizeSeries(std::vector<syz_SineBankWave> *waves) { | ||
| double gsum = 0.0; | ||
| for (auto &w : *waves) { | ||
| gsum += w.gain; | ||
| } | ||
| double nf = 1.0 / gsum; | ||
| for (auto &w : *waves) { | ||
| w.gain *= nf; | ||
| } | ||
| } | ||
| static std::vector<syz_SineBankWave> buildSquareSeries(unsigned int partials) { | ||
| std::vector<syz_SineBankWave> out{}; | ||
| for (unsigned int p = 0; p < partials; p++) { | ||
| double gain = 1.0 / (2 * p + 1); | ||
| out.emplace_back(syz_SineBankWave{(double)(p * 2 + 1), 0.0, gain}); | ||
| } | ||
| sigmaApproximate(&out); | ||
| normalizeSeries(&out); | ||
| return out; | ||
| } | ||
| static std::vector<syz_SineBankWave> buildTriangleSeries(unsigned int partials) { | ||
| std::vector<syz_SineBankWave> out; | ||
| double sign = 1.0; | ||
| for (unsigned int i = 0; i < partials; i++) { | ||
| double n = 2 * i + 1; | ||
| out.emplace_back(syz_SineBankWave{(double)n, 0.0, sign / (n * n)}); | ||
| sign *= -1.0; | ||
| } | ||
| // Don't sigma approximate: it makes things much worse audibly at low partial counts. | ||
| normalizeSeries(&out); | ||
| return out; | ||
| } | ||
| static std::vector<syz_SineBankWave> buildSawtoothSeries(unsigned int partials) { | ||
| std::vector<syz_SineBankWave> out; | ||
| // `$ x(t)=-{\frac {2}{\pi }}\sum _{k=1}^{\infty }{\frac {{\left(-1\right)}^{k}}{k}}\sin \left(2\pi kt\right) $` | ||
| // but we move the outer negation into the sum and let our normalizer handle the multiple on the gain. | ||
| double sign = -1.0; | ||
| for (unsigned int i = 1; i <= partials; i++) { | ||
| out.emplace_back(syz_SineBankWave{(double)i, 0.0, sign / (double)i}); | ||
| sign *= -1.0; | ||
| } | ||
| // Don't sigma approximate: it makes things much worse audibly at low partial counts. | ||
| normalizeSeries(&out); | ||
| return out; | ||
| } | ||
| } // namespace synthizer | ||
| using namespace synthizer; | ||
| SYZ_CAPI void syz_initSineBankConfig(struct syz_SineBankConfig *cfg) { *cfg = syz_SineBankConfig{}; } | ||
| SYZ_CAPI syz_ErrorCode syz_createFastSineBankGenerator(syz_Handle *out, syz_Handle context, | ||
| const struct syz_SineBankConfig *bank_config, void *config, | ||
| void *userdata, | ||
| syz_UserdataFreeCallback *userdata_free_callback) { | ||
| SYZ_PROLOGUE(void) config; | ||
| auto ctx = fromC<Context>(context); | ||
| auto x = ctx->createObject<FastSineBankGenerator>(bank_config); | ||
| *out = toC(x); | ||
| return syz_handleSetUserdata(*out, userdata, userdata_free_callback); | ||
| SYZ_EPILOGUE | ||
| } | ||
| SYZ_CAPI syz_ErrorCode syz_createFastSineBankGeneratorSine(syz_Handle *out, syz_Handle context, | ||
| double initial_frequency, void *config, void *userdata, | ||
| syz_UserdataFreeCallback *userdata_free_callback) { | ||
| static const struct syz_SineBankWave wave { 1.0, 0.0, 1.0 }; | ||
| struct syz_SineBankConfig cfg; | ||
| cfg.waves = &wave; | ||
| cfg.wave_count = 1; | ||
| cfg.initial_frequency = initial_frequency; | ||
| return syz_createFastSineBankGenerator(out, context, &cfg, config, userdata, userdata_free_callback); | ||
| } | ||
| static syz_ErrorCode createSineBankFromVec(syz_Handle *out, syz_Handle context, double initial_frequency, | ||
| std::vector<syz_SineBankWave> *waves, void *userdata, | ||
| syz_UserdataFreeCallback *userdata_free_callback) { | ||
| struct syz_SineBankConfig cfg; | ||
| cfg.waves = &(*waves)[0]; | ||
| cfg.wave_count = waves->size(); | ||
| cfg.initial_frequency = initial_frequency; | ||
| return syz_createFastSineBankGenerator(out, context, &cfg, NULL, userdata, userdata_free_callback); | ||
| } | ||
| SYZ_CAPI syz_ErrorCode syz_createFastSineBankGeneratorSquare(syz_Handle *out, syz_Handle context, | ||
| double initial_frequency, unsigned int partials, | ||
| void *config, void *userdata, | ||
| syz_UserdataFreeCallback *userdata_free_callback) { | ||
| SYZ_PROLOGUE(void) config; | ||
| auto waves = buildSquareSeries(partials); | ||
| return createSineBankFromVec(out, context, initial_frequency, &waves, userdata, userdata_free_callback); | ||
| SYZ_EPILOGUE | ||
| } | ||
| SYZ_CAPI syz_ErrorCode syz_createFastSineBankGeneratorTriangle(syz_Handle *out, syz_Handle context, | ||
| double initial_frequency, unsigned int partials, | ||
| void *config, void *userdata, | ||
| syz_UserdataFreeCallback *userdata_free_callback) { | ||
| SYZ_PROLOGUE(void) config; | ||
| auto waves = buildTriangleSeries(partials); | ||
| return createSineBankFromVec(out, context, initial_frequency, &waves, userdata, userdata_free_callback); | ||
| SYZ_EPILOGUE | ||
| } | ||
| SYZ_CAPI syz_ErrorCode syz_createFastSineBankGeneratorSaw(syz_Handle *out, syz_Handle context, double initial_frequency, | ||
| unsigned int partials, void *config, void *userdata, | ||
| syz_UserdataFreeCallback *userdata_free_callback) { | ||
| SYZ_PROLOGUE(void) config; | ||
| auto waves = buildSawtoothSeries(partials); | ||
| return createSineBankFromVec(out, context, initial_frequency, &waves, userdata, userdata_free_callback); | ||
| SYZ_EPILOGUE | ||
| } |
| /** | ||
| * Tests the accuracy of the FastSineBank. | ||
| * | ||
| * Constants are hardcoded in main. | ||
| * */ | ||
| #include <cstdio> | ||
| #include <math.h> | ||
| #include <vector> | ||
| #include "synthizer/config.hpp" | ||
| #include "synthizer/fast_sine_bank.hpp" | ||
| #include "synthizer/math.hpp" | ||
| using namespace synthizer; | ||
| class SlowSineBank { | ||
| public: | ||
| SlowSineBank(double _frequency) : frequency(_frequency) {} | ||
| void fillBlock(float *block); | ||
| void addWave(const SineWaveConfig &wave) { this->waves.push_back(wave); } | ||
| double frequency; | ||
| std::vector<SineWaveConfig> waves; | ||
| double time = 0.0; | ||
| }; | ||
| void SlowSineBank::fillBlock(float *block) { | ||
| // To match FastSineBank as much as possible, we increment time as we go, then resync it at the end. | ||
| double old_t = this->time; | ||
| for (std::size_t i = 0; i < config::BLOCK_SIZE; i++) { | ||
| for (auto &w : this->waves) { | ||
| double freq = w.freq_mul * this->frequency; | ||
| double t = 2 * PI * (freq * this->time + w.phase); | ||
| block[i] += ((float)w.gain) * sinf(t); | ||
| } | ||
| this->time = fmod(this->time + 1.0 / config::SR, 1.0); | ||
| } | ||
| this->time = fmod(old_t + config::BLOCK_SIZE / (double)config::SR, 1.0); | ||
| } | ||
| int main() { | ||
| FastSineBank fast{300.0}; | ||
| SlowSineBank slow{300.0}; | ||
| std::vector<SineWaveConfig> waves{{1.0, 0.0, 0.5}, {2.0, 0.1, 0.2}, {3.0, 0.03, 0.2}, {4.0, 0.0, 0.01}}; | ||
| for (auto &w : waves) { | ||
| slow.addWave(w); | ||
| fast.addWave(w); | ||
| } | ||
| // This value was determined empirically, and then we decided whether we were good with it or not after the fact. | ||
| double max_diff = 0.0002; | ||
| for (std::size_t block = 0; block < 1000000; block++) { | ||
| float slow_res[config::BLOCK_SIZE] = {0.0f}; | ||
| float fast_res[config::BLOCK_SIZE] = {0.0f}; | ||
| slow.fillBlock(slow_res); | ||
| fast.fillBlock<config::BLOCK_SIZE, false>(fast_res); | ||
| for (std::size_t off = 0; off < config::BLOCK_SIZE; off++) { | ||
| double diff = slow_res[off] - fast_res[off]; | ||
| if (fabs(diff) > max_diff) { | ||
| printf("Failed\n"); | ||
| printf("Diff at %zu is %f\n", block * config::BLOCK_SIZE + off, diff); | ||
| printf("slow=%f fast=%f\n", slow_res[off], fast_res[off]); | ||
| printf("Block %zu offset %zu\n", block, off); | ||
| return 1; | ||
| } | ||
| } | ||
| } | ||
| return 0; | ||
| } |
+1
-1
| Metadata-Version: 1.0 | ||
| Name: synthizer | ||
| Version: 0.11.6 | ||
| Version: 0.12.0 | ||
| Summary: UNKNOWN | ||
@@ -5,0 +5,0 @@ Home-page: https://synthizer.github.io |
+1
-1
@@ -11,3 +11,3 @@ import os | ||
| VERSION = "0.11.6" | ||
| VERSION = "0.12.0" | ||
@@ -14,0 +14,0 @@ # A helper for rmtree. On Windows, read-only files can't be deleted by rmtree, so we make them not readonly. |
@@ -9,3 +9,3 @@ cmake_minimum_required(VERSION 3.15.0) | ||
| # Synthizer version. | ||
| add_compile_definitions(SYZ_MAJOR=0 SYZ_MINOR=11 SYZ_PATCH=5) | ||
| add_compile_definitions(SYZ_MAJOR=0 SYZ_MINOR=11 SYZ_PATCH=6) | ||
@@ -149,2 +149,3 @@ include(CTest) | ||
| src/generators/buffer.cpp | ||
| src/generators/fast_sine_bank.cpp | ||
| src/generators/noise.cpp | ||
@@ -206,2 +207,3 @@ src/generators/streaming.cpp | ||
| example(events cpp) | ||
| example(fast_sine_bank cpp) | ||
| example(load_libsndfile c) | ||
@@ -218,2 +220,3 @@ example(play_note c) | ||
| target_link_libraries(delay_line_test synthizer) | ||
| add_test(NAME delay_line COMMAND delay_line_test) | ||
@@ -240,5 +243,7 @@ add_executable(file_test file_test.cpp) | ||
| target_link_libraries(test_latch synthizer) | ||
| add_test(NAME latch COMMAND test_latch) | ||
| add_executable(test_verify_properties test/verify_properties.cpp) | ||
| target_link_libraries(test_verify_properties synthizer) | ||
| add_test(NAME verify_properties COMMAND test_verify_properties) | ||
@@ -251,19 +256,17 @@ # can't be an actual unit test until we have silent contexts. | ||
| target_link_libraries(test_generation_thread synthizer) | ||
| add_test(NAME generation_thread COMMAND test_generation_thread) | ||
| add_executable(test_double_refcount test/double_refcount.c) | ||
| target_link_libraries(test_double_refcount synthizer) | ||
| add_test(NAME double_refcount COMMAND test_double_refcount) | ||
| add_executable(test_property_automation_timeline test/property_automation_timeline.cpp) | ||
| target_link_libraries(test_property_automation_timeline synthizer) | ||
| add_test(NAME property_automation_timeline COMMAND test_property_automation_timeline) | ||
| add_executable(test_seeking test/seeking.cpp) | ||
| target_link_libraries(test_seeking synthizer) | ||
| add_test(NAME delay_line COMMAND delay_line_test) | ||
| add_test(NAME latch COMMAND test_latch) | ||
| add_test(NAME verify_properties COMMAND test_verify_properties) | ||
| add_test(NAME generation_thread COMMAND test_generation_thread) | ||
| add_test(NAME double_refcount COMMAND test_double_refcount) | ||
| add_test(NAME property_automation_timeline COMMAND test_property_automation_timeline) | ||
| add_executable(test_fast_sine_accuracy test/fast_sine_accuracy.cpp) | ||
| target_link_libraries(test_fast_sine_accuracy synthizer) | ||
| endif() | ||
@@ -270,0 +273,0 @@ |
@@ -21,2 +21,3 @@ #pragma once | ||
| SYZ_OTYPE_AUTOMATION_BATCH, | ||
| SYZ_OTYPE_FAST_SINE_BANK_GENERATOR, | ||
| }; | ||
@@ -100,2 +101,4 @@ | ||
| SYZ_P_SUGGESTED_AUTOMATION_TIME, | ||
| SYZ_P_FREQUENCY, | ||
| }; | ||
@@ -102,0 +105,0 @@ |
@@ -266,2 +266,35 @@ #pragma once | ||
| struct syz_SineBankWave { | ||
| double frequency_mul; | ||
| double phase; | ||
| double gain; | ||
| }; | ||
| struct syz_SineBankConfig { | ||
| const struct syz_SineBankWave *waves; | ||
| unsigned long long wave_count; | ||
| double initial_frequency; | ||
| }; | ||
| SYZ_CAPI void syz_initSineBankConfig(struct syz_SineBankConfig *cfg); | ||
| SYZ_CAPI syz_ErrorCode syz_createFastSineBankGenerator(syz_Handle *out, syz_Handle context, | ||
| const struct syz_SineBankConfig *bank_config, void *config, | ||
| void *userdata, | ||
| syz_UserdataFreeCallback *userdata_free_callback); | ||
| SYZ_CAPI syz_ErrorCode syz_createFastSineBankGeneratorSine(syz_Handle *out, syz_Handle context, | ||
| double initial_frequency, void *config, void *userdata, | ||
| syz_UserdataFreeCallback *userdata_free_callback); | ||
| SYZ_CAPI syz_ErrorCode syz_createFastSineBankGeneratorTriangle(syz_Handle *out, syz_Handle context, | ||
| double initial_frequency, unsigned int partials, | ||
| void *config, void *userdata, | ||
| syz_UserdataFreeCallback *userdata_free_callback); | ||
| SYZ_CAPI syz_ErrorCode syz_createFastSineBankGeneratorSquare(syz_Handle *out, syz_Handle context, | ||
| double initial_frequency, unsigned int partials, | ||
| void *config, void *userdata, | ||
| syz_UserdataFreeCallback *userdata_free_callback); | ||
| SYZ_CAPI syz_ErrorCode syz_createFastSineBankGeneratorSaw(syz_Handle *out, syz_Handle context, double initial_frequency, | ||
| unsigned int partials, void *config, void *userdata, | ||
| syz_UserdataFreeCallback *userdata_free_callback); | ||
| struct syz_RouteConfig { | ||
@@ -268,0 +301,0 @@ double gain; |
@@ -110,7 +110,8 @@ #pragma once | ||
| std::size_t can_lose = this->current_item - HISTORY_LENGTH; | ||
| auto keep_start = this->items.begin() + can_lose; | ||
| std::size_t drop_count = this->current_item - HISTORY_LENGTH; | ||
| auto keep_start = this->items.begin() + drop_count; | ||
| std::copy(keep_start, this->items.end(), this->items.begin()); | ||
| this->items.erase(this->items.begin() + HISTORY_LENGTH, this->items.end()); | ||
| this->current_item = HISTORY_LENGTH - 1; | ||
| this->items.erase(this->items.end() - drop_count, this->items.end()); | ||
| // We have indices 0 to `HISTORY_LENGTH - 1` as the history; the actual current item is 1 after that. | ||
| this->current_item = HISTORY_LENGTH; | ||
| } | ||
@@ -117,0 +118,0 @@ |
@@ -103,4 +103,6 @@ #pragma once | ||
| #define SINE_BANK_GENERATOR_PROPERTIES DOUBLE_P(SYZ_P_FREQUENCY, frequency, Frequency, 0.0, P_DOUBLE_MAX, 440.0) | ||
| #ifdef __cplusplus | ||
| } | ||
| #endif |
@@ -7,4 +7,5 @@ #include "synthizer.h" | ||
| #include <cmath> | ||
| #include <cstdio> | ||
| #include <math.h> | ||
| #include <stdio.h> | ||
| #include <stdlib.h> | ||
| #include <vector> | ||
@@ -14,3 +15,3 @@ | ||
| bool floatCmp(double a, double b) { return std::abs(a - b) < 0.000001; } | ||
| bool floatCmp(double a, double b) { return abs(a - b) < 0.000001; } | ||
@@ -23,3 +24,3 @@ struct Point { | ||
| int main() { | ||
| void testCurve() { | ||
| double tick_delta = config::BLOCK_SIZE / (double)config::SR; | ||
@@ -64,7 +65,7 @@ double time = 0.0; | ||
| printf("Expected %f but got %f at time %f\n", exp, x[0], time); | ||
| return 1; | ||
| exit(EXIT_FAILURE); | ||
| } | ||
| } else { | ||
| printf("Timeline ended early at time %f\n", time); | ||
| return 1; | ||
| exit(EXIT_FAILURE); | ||
| } | ||
@@ -74,3 +75,63 @@ } | ||
| printf("Success\n"); | ||
| } | ||
| class IdentityPoint { | ||
| public: | ||
| IdentityPoint(double t) : value(t) {} | ||
| double getTime() { return this->value; } | ||
| double value; | ||
| }; | ||
| /** | ||
| * Test a timeline which "runs ahead". Exercises logic which gets rid of unneeded points. | ||
| * */ | ||
| void testLongTimeline() { | ||
| auto tl = GenericTimeline<IdentityPoint, 10>(); | ||
| unsigned int i = 0, last_added = 0; | ||
| // Add an initial 10 points. | ||
| for (i = 0; i < 10; i++) { | ||
| tl.addItem(IdentityPoint((double)i)); | ||
| } | ||
| // We want the 10th point for convenience; always having one in the future for the next loop avoids a weird special | ||
| // case where the first iteration returns the wrong value w.r.t. what we check because there is no point "after" the | ||
| // start time. | ||
| last_added = i; | ||
| tl.addItem(IdentityPoint(last_added)); | ||
| for (; i < 10000; i++) { | ||
| // Drive the timeline to time i-0.5, which gives us the next point as i. | ||
| tl.tick((double)i - 0.5); | ||
| // We know there are at least 10 points; check them all. | ||
| for (unsigned int d = 0; d < 10; d++) { | ||
| double p = (double)i - d; | ||
| auto val = tl.getItem(-d); | ||
| if (!val) { | ||
| printf("Expected point at %f but didn't find one", p); | ||
| exit(EXIT_FAILURE); | ||
| } | ||
| if ((*val)->value != (double)p) { | ||
| printf("Expected %f but got %f at %i\n", (double)p, (*val)->value, -d); | ||
| exit(EXIT_FAILURE); | ||
| } | ||
| } | ||
| // So the timeline always grows, add two items. | ||
| for (unsigned int _unused = 0; _unused < 2; _unused++) { | ||
| last_added += 1; | ||
| tl.addItem(IdentityPoint((double)last_added)); | ||
| } | ||
| } | ||
| printf("Success\n"); | ||
| } | ||
| int main() { | ||
| printf("Running curve test\n"); | ||
| testCurve(); | ||
| printf("Testing long GenericTimeline\n"); | ||
| testLongTimeline(); | ||
| return 0; | ||
| } |
| Metadata-Version: 1.0 | ||
| Name: synthizer | ||
| Version: 0.11.6 | ||
| Version: 0.12.0 | ||
| Summary: UNKNOWN | ||
@@ -5,0 +5,0 @@ Home-page: https://synthizer.github.io |
@@ -23,2 +23,3 @@ MANIFEST.in | ||
| synthizer-vendored/examples/example_common.h | ||
| synthizer-vendored/examples/fast_sine_bank.cpp | ||
| synthizer-vendored/examples/load_libsndfile.c | ||
@@ -59,2 +60,3 @@ synthizer-vendored/examples/play_note.c | ||
| synthizer-vendored/include/synthizer/faders.hpp | ||
| synthizer-vendored/include/synthizer/fast_sine_bank.hpp | ||
| synthizer-vendored/include/synthizer/filter_design.hpp | ||
@@ -107,2 +109,3 @@ synthizer-vendored/include/synthizer/generation_thread.hpp | ||
| synthizer-vendored/include/synthizer/generators/buffer.hpp | ||
| synthizer-vendored/include/synthizer/generators/fast_sine_bank.hpp | ||
| synthizer-vendored/include/synthizer/generators/noise.hpp | ||
@@ -158,2 +161,3 @@ synthizer-vendored/include/synthizer/generators/streaming.hpp | ||
| synthizer-vendored/src/generators/buffer.cpp | ||
| synthizer-vendored/src/generators/fast_sine_bank.cpp | ||
| synthizer-vendored/src/generators/noise.cpp | ||
@@ -173,2 +177,3 @@ synthizer-vendored/src/generators/streaming.cpp | ||
| synthizer-vendored/test/effect_connection.cpp | ||
| synthizer-vendored/test/fast_sine_accuracy.cpp | ||
| synthizer-vendored/test/filter_repl.cpp | ||
@@ -175,0 +180,0 @@ synthizer-vendored/test/generation_thread.cpp |
+327
-106
@@ -5,2 +5,3 @@ #cython: auto_pickle=False | ||
| import threading | ||
| from collections import abc | ||
| from enum import Enum | ||
@@ -14,2 +15,3 @@ import sys | ||
| from cpython.ref cimport PyObject, Py_DECREF, Py_INCREF | ||
| from weakref import ref | ||
@@ -33,2 +35,3 @@ # We want the ability to acquire and release the GIL, which means making sure it's initialized. | ||
| cdef class SynthizerError(Exception): | ||
@@ -49,2 +52,3 @@ cdef str message | ||
| cdef _checked(x): | ||
@@ -59,99 +63,135 @@ if x != 0: | ||
| # These are descriptors for properties so that we don't have to continually deal with defining 6 line method pairs. | ||
| # These might eventually prove to be too slow, in which case we will have to convert to get_xxx and set_xxx methods. | ||
| # Unfortunately Cython doesn't give us a convenient way to generate such at compile time. | ||
| # Fortunately there's a way to migrate people forward if we must. | ||
| # This is the base class for all Synthizer properties | ||
| cdef class _PropertyBase: | ||
| def __delete__(self, instance): | ||
| raise RuntimeError("Deleting attributes from Synthizer objects is not possible") | ||
| cdef object _instance | ||
| cdef int property | ||
| def __init__(self, instance, property): | ||
| self._instance = ref(instance) | ||
| self.property = property | ||
| def _get_handle_checked(self): | ||
| # Any attempt to reference a non-existing object should raise an error | ||
| obj = self._instance() | ||
| if obj is None: | ||
| raise RuntimeError("Object no longer exists.") | ||
| handle = obj._get_handle() | ||
| if handle == 0: | ||
| raise RuntimeError("Object no longer exists.") | ||
| return handle | ||
| def _get_property(self): | ||
| return self.property | ||
| cdef class IntProperty(_PropertyBase): | ||
| cdef int property | ||
| cdef object conv_in | ||
| cdef object conv_out | ||
| def __init__(self, int property, conv_in = lambda x: x, conv_out = lambda x: x): | ||
| self.property = property | ||
| def __init__(self, object instance, int property, conv_in = lambda x: x, conv_out = lambda x: x): | ||
| super().__init__(instance, property) | ||
| self.conv_in = conv_in | ||
| self.conv_out = conv_out | ||
| def __get__(self, _BaseObject instance, owner): | ||
| @property | ||
| def value(self): | ||
| cdef int val | ||
| _checked(syz_getI(&val, instance.handle, self.property)) | ||
| handle = self._get_handle_checked() | ||
| _checked(syz_getI(&val, handle, self.property)) | ||
| return self.conv_out(val) | ||
| def __set__(self, _BaseObject instance, value): | ||
| _checked(syz_setI(instance.handle, self.property, self.conv_in(value))) | ||
| @value.setter | ||
| def value(self, value): | ||
| handle = self._get_handle_checked() | ||
| _checked(syz_setI(handle, self.property, self.conv_in(value))) | ||
| def enum_property(v, e): | ||
| return IntProperty(v, conv_in = lambda x: x.value, conv_out = lambda x: e(x)) | ||
| def enum_property(instance, v, e): | ||
| return IntProperty(instance, v, conv_in = lambda x: x.value, conv_out = lambda x: e(x)) | ||
| cdef class DoubleProperty(_PropertyBase): | ||
| cdef int property | ||
| def __init__(self, int property): | ||
| self.property = property | ||
| def __get__(self, _BaseObject instance, owner): | ||
| @property | ||
| def value(self): | ||
| cdef double val | ||
| _checked(syz_getD(&val, instance.handle, self.property)) | ||
| handle = self._get_handle_checked() | ||
| _checked(syz_getD(&val, handle, self.property)) | ||
| return val | ||
| def __set__(self, _BaseObject instance, double value): | ||
| _checked(syz_setD(instance.handle, self.property, value)) | ||
| @value.setter | ||
| def value(self, double value): | ||
| handle = self._get_handle_checked() | ||
| _checked(syz_setD(handle, self.property, value)) | ||
| cdef class Double3Property(_PropertyBase): | ||
| cdef int property | ||
| def __init__(self, int property): | ||
| self.property = property | ||
| def __get__(self, _BaseObject instance, owner): | ||
| @property | ||
| def value(self): | ||
| cdef double x, y, z | ||
| _checked(syz_getD3(&x, &y, &z, instance.handle, self.property)) | ||
| handle = self._get_handle_checked() | ||
| _checked(syz_getD3(&x, &y, &z, handle, self.property)) | ||
| return (x, y, z) | ||
| def __set__(self, _BaseObject instance, value): | ||
| _checked(syz_setD3(instance.handle, self.property, value[0], value[1], value[2])) | ||
| @value.setter | ||
| def value(self, value): | ||
| cdef double x, y, z | ||
| handle = self._get_handle_checked() | ||
| try: | ||
| x, y, z = value | ||
| except ValueError as e: | ||
| raise ValueError("Three doubles are required for Double3Property.") | ||
| _checked(syz_setD3(handle, self.property, x, y, z)) | ||
| cdef class Double6Property(_PropertyBase): | ||
| cdef int property | ||
| def __init__(self, int property): | ||
| self.property = property | ||
| def __get__(self, _BaseObject instance, owner): | ||
| @property | ||
| def value(self): | ||
| cdef double x1, y1, z1, x2, y2, z2 | ||
| _checked(syz_getD6(&x1, &y1, &z1, &x2, &y2, &z2, instance.handle, self.property)) | ||
| handle = self._get_handle_checked() | ||
| _checked(syz_getD6(&x1, &y1, &z1, &x2, &y2, &z2, handle, self.property)) | ||
| return (x1, y1, z1, x2, y2, z2) | ||
| def __set__(self, _BaseObject instance, value): | ||
| _checked(syz_setD6(instance.handle, self.property, value[0], value[1], value[2], value[3], value[4], value[5])) | ||
| @value.setter | ||
| def value(self, value): | ||
| cdef double x1, y1, z1, x2, y2, z2 | ||
| handle = self._get_handle_checked() | ||
| try: | ||
| x1, y1, z1, x2, y2, z2 = value | ||
| except ValueError as e: | ||
| raise ValueError("Six doubles are required for Double6Property") | ||
| _checked(syz_setD6(handle, self.property, x1, y1, z1, x2, y2, z2)) | ||
| cdef class BiquadProperty(_PropertyBase): | ||
| cdef int property | ||
| def __init__(self, int property): | ||
| self.property = property | ||
| @property | ||
| def value(self): | ||
| raise NotImplementedError("Filter properties cannot be read") | ||
| def __get__(self, _BaseObject instance, owner): | ||
| raise NotImplementedError("Filter properties cannot be read") | ||
| @value.setter | ||
| def value(self, BiquadConfig value): | ||
| handle = self._get_handle_checked() | ||
| _checked(syz_setBiquad(handle, self.property, &value.config)) | ||
| def __set__(self, _BaseObject instance, BiquadConfig value): | ||
| _checked(syz_setBiquad(instance.handle, self.property, &value.config)) | ||
| cdef class ObjectProperty(_PropertyBase): | ||
| cdef class ObjectProperty(_PropertyBase): | ||
| cdef int property | ||
| cdef object cls | ||
| def __init__(self, int property, cls): | ||
| self.property = property | ||
| def __init__(self, object instance, int property, cls): | ||
| super().__init__(instance, property) | ||
| self.cls = cls | ||
| def __get__(self, _BaseObject instance, owner): | ||
| raise NotImplementedError("Can't read object properties") | ||
| def __set__(self, _BaseObject instance, _BaseObject value): | ||
| _checked(syz_setO(instance.handle, self.property, value.handle if value else 0)) | ||
| @property | ||
| def value(self): | ||
| raise NotImplementedError("Can't read object properties") | ||
| @value.setter | ||
| def value(self, _BaseObject value): | ||
| handle = self._get_handle_checked() | ||
| _checked(syz_setO(handle, self.property, value.handle if value else 0)) | ||
| class LogLevel(Enum): | ||
@@ -163,2 +203,3 @@ ERROR = SYZ_LOG_LEVEL_ERROR | ||
| class LoggingBackend(Enum): | ||
@@ -168,2 +209,3 @@ NONE = SYZ_LOGGING_BACKEND_NONE | ||
| cpdef initialize(log_level=DEFAULT_VALUE, logging_backend=DEFAULT_VALUE, | ||
@@ -211,2 +253,3 @@ libsndfile_path=DEFAULT_VALUE): | ||
| class PannerStrategy(Enum): | ||
@@ -217,2 +260,3 @@ DELEGATE = SYZ_PANNER_STRATEGY_DELEGATE | ||
| class DistanceModel(Enum): | ||
@@ -224,2 +268,3 @@ NONE = SYZ_DISTANCE_MODEL_NONE | ||
| cdef object _objects_by_handle = dict() | ||
@@ -243,2 +288,3 @@ cdef object _objects_by_handle_mutex = threading.Lock() | ||
| cdef class _UserdataBox: | ||
@@ -248,2 +294,3 @@ """An internal box for containing userdata. This exists so that we can have | ||
| buffers alive for stream handles.""" | ||
| cdef object userdata | ||
@@ -257,4 +304,7 @@ # Used by StreamHandle | ||
| cdef class _BaseObject: | ||
| cdef syz_Handle handle | ||
| cdef object __weakref__ | ||
@@ -269,2 +319,9 @@ def __init__(self, syz_Handle handle): | ||
| def __setattr__(self, name, value): | ||
| # If an object of type _PropertyBase matching name already is on this object, prevent it from being reassigned | ||
| obj = getattr(self, name, None) | ||
| if isinstance(obj, _PropertyBase): | ||
| raise RuntimeError("You cannot directly reassign property objects.") | ||
| super().__setattr__(name, value) | ||
| def dec_ref(self): | ||
@@ -282,2 +339,5 @@ """Decrement the reference count. Must be called in order to not leak Synthizer objects.""" | ||
| cpdef syz_Handle _get_handle(self): | ||
| return self.handle | ||
| cdef object _get_userdata_box(self): | ||
@@ -305,5 +365,13 @@ cdef void *userdata | ||
| cdef class Pausable(_BaseObject): | ||
| """Base class for anything which can be paused. Adds pause and play methods.""" | ||
| cdef public DoubleProperty current_time, suggested_automation_time | ||
| def __init__(self, syz_Handle handle): | ||
| super().__init__(handle) | ||
| self.current_time = DoubleProperty(self, SYZ_P_CURRENT_TIME) | ||
| self.suggested_automation_time = DoubleProperty(self, SYZ_P_SUGGESTED_AUTOMATION_TIME) | ||
| def play(self): | ||
@@ -315,6 +383,7 @@ _checked(syz_play(self.handle)) | ||
| cdef class Event: | ||
| """Base class for all Synthizer events""" | ||
| cpdef public Context context | ||
| cpdef public object source | ||
| cdef public Context context | ||
| cdef public object source | ||
@@ -329,5 +398,15 @@ def __init__(self, context, source): | ||
| cdef class LoopedEvent(Event): | ||
| pass | ||
| cdef class UserAutomationEvent(Event): | ||
| cdef public unsigned long long param | ||
| def __init__(self, context, source, param): | ||
| super().__init__(context, source) | ||
| self.param = param | ||
| cdef _convert_event(syz_Event event): | ||
@@ -338,4 +417,8 @@ if event.type == SYZ_EVENT_TYPE_FINISHED: | ||
| return LoopedEvent(_handle_to_object(event.context), _handle_to_object(event.source)) | ||
| elif event.type == SYZ_EVENT_TYPE_USER_AUTOMATION: | ||
| return UserAutomationEvent(_handle_to_object(event.context), _handle_to_object(event.source), event.payload.user_automation.param) | ||
| cdef class BiquadConfig: | ||
| cdef syz_BiquadConfig config | ||
@@ -372,2 +455,3 @@ | ||
| cdef class Context(Pausable): | ||
@@ -380,2 +464,7 @@ """The Synthizer context represents an open audio device and groups all Synthizer objects created with it into one unit. | ||
| cdef public DoubleProperty gain, default_distance_ref, default_distance_max, default_rolloff, default_closeness_boost, default_closeness_boost_distance | ||
| cdef public Double3Property position | ||
| cdef public Double6Property orientation | ||
| cdef public IntProperty default_distance_model, default_panner_strategy | ||
| def __init__(self, enable_events=False): | ||
@@ -387,14 +476,13 @@ cdef syz_Handle handle | ||
| self.enable_events() | ||
| self.gain = DoubleProperty(self, SYZ_P_GAIN) | ||
| self.position = Double3Property(self, SYZ_P_POSITION) | ||
| self.orientation = Double6Property(self, SYZ_P_ORIENTATION) | ||
| self.default_distance_model = enum_property(self, SYZ_P_DEFAULT_DISTANCE_MODEL, lambda x: DistanceModel(x)) | ||
| self.default_distance_ref = DoubleProperty(self, SYZ_P_DEFAULT_DISTANCE_REF) | ||
| self.default_distance_max = DoubleProperty(self, SYZ_P_DEFAULT_DISTANCE_MAX) | ||
| self.default_rolloff = DoubleProperty(self, SYZ_P_DEFAULT_ROLLOFF) | ||
| self.default_closeness_boost = DoubleProperty(self, SYZ_P_DEFAULT_CLOSENESS_BOOST) | ||
| self.default_closeness_boost_distance = DoubleProperty(self, SYZ_P_DEFAULT_CLOSENESS_BOOST_DISTANCE) | ||
| self.default_panner_strategy = enum_property(self, SYZ_P_DEFAULT_PANNER_STRATEGY, lambda x: PannerStrategy(x)) | ||
| gain = DoubleProperty(SYZ_P_GAIN) | ||
| position = Double3Property(SYZ_P_POSITION) | ||
| orientation = Double6Property(SYZ_P_ORIENTATION) | ||
| default_distance_model = enum_property(SYZ_P_DEFAULT_DISTANCE_MODEL, lambda x: DistanceModel(x)) | ||
| default_distance_ref = DoubleProperty(SYZ_P_DEFAULT_DISTANCE_REF) | ||
| default_distance_max = DoubleProperty(SYZ_P_DEFAULT_DISTANCE_MAX) | ||
| default_rolloff = DoubleProperty(SYZ_P_DEFAULT_ROLLOFF) | ||
| default_closeness_boost = DoubleProperty(SYZ_P_DEFAULT_CLOSENESS_BOOST) | ||
| default_closeness_boost_distance = DoubleProperty(SYZ_P_DEFAULT_CLOSENESS_BOOST_DISTANCE) | ||
| default_panner_strategy = enum_property(SYZ_P_DEFAULT_PANNER_STRATEGY, lambda x: PannerStrategy(x)) | ||
| cpdef config_route(self, _BaseObject output, _BaseObject input, gain = 1.0, fade_time = 0.01, BiquadConfig filter = None): | ||
@@ -439,2 +527,3 @@ cdef syz_RouteConfig config | ||
| # Used to keep errors alive per the custom stream error lifetime rules in synthizer.h. | ||
@@ -567,2 +656,3 @@ cdef class WrappedStream: | ||
| cdef class StreamHandle(_BaseObject): | ||
@@ -625,9 +715,19 @@ """Wraps the C API concept of a StreamHandle, which may be created in a variety of ways.""" | ||
| cdef class Generator(Pausable): | ||
| """Base class for all generators.""" | ||
| pitch_bend = DoubleProperty(SYZ_P_PITCH_BEND) | ||
| gain = DoubleProperty(SYZ_P_GAIN) | ||
| cdef public DoubleProperty gain, pitch_bend | ||
| def __init__(self, syz_Handle handle): | ||
| super().__init__(handle) | ||
| self.pitch_bend = DoubleProperty(self, SYZ_P_PITCH_BEND) | ||
| self.gain = DoubleProperty(self, SYZ_P_GAIN) | ||
| cdef class StreamingGenerator(Generator): | ||
| cdef public IntProperty looping | ||
| cdef public DoubleProperty playback_position | ||
| def __init__(self, _handle = None): | ||
@@ -637,4 +737,5 @@ if _handle is None: | ||
| super().__init__(_handle) | ||
| self.playback_position = DoubleProperty(self, SYZ_P_PLAYBACK_POSITION) | ||
| self.looping = IntProperty(self, SYZ_P_LOOPING, conv_in = int, conv_out = bool) | ||
| @staticmethod | ||
@@ -667,3 +768,2 @@ def from_stream_params(context, protocol, path): | ||
| @staticmethod | ||
@@ -675,4 +775,2 @@ def from_stream_handle(Context context, StreamHandle stream): | ||
| playback_position = DoubleProperty(SYZ_P_PLAYBACK_POSITION) | ||
| looping = IntProperty(SYZ_P_LOOPING, conv_in = int, conv_out = bool) | ||
@@ -682,2 +780,10 @@ cdef class Source(Pausable): | ||
| cdef public DoubleProperty gain | ||
| cdef public BiquadProperty filter | ||
| def __init__(self, syz_Handle handle): | ||
| super().__init__(handle) | ||
| self.gain = DoubleProperty(self, SYZ_P_GAIN) | ||
| self.filter = BiquadProperty(self, SYZ_P_FILTER) | ||
| cpdef add_generator(self, generator): | ||
@@ -693,5 +799,2 @@ """Add a generator to this source.""" | ||
| gain = DoubleProperty(SYZ_P_GAIN) | ||
| filter = BiquadProperty(SYZ_P_FILTER) | ||
| cdef class DirectSource(Source) : | ||
@@ -704,3 +807,7 @@ def __init__(self, context): | ||
| cdef class AngularPannedSource(Source): | ||
| cdef public DoubleProperty azimuth, elevation | ||
| def __init__(self, context, panner_strategy = PannerStrategy.DELEGATE, azimuth=0.0, elevation=0.0): | ||
@@ -711,7 +818,10 @@ cdef syz_Handle ctx = context._get_handle_checked(Context) | ||
| super().__init__(out) | ||
| self.azimuth = DoubleProperty(self, SYZ_P_AZIMUTH) | ||
| self.elevation = DoubleProperty(self, SYZ_P_ELEVATION) | ||
| azimuth = DoubleProperty(SYZ_P_AZIMUTH) | ||
| elevation = DoubleProperty(SYZ_P_ELEVATION) | ||
| cdef class ScalarPannedSource(Source): | ||
| cdef public DoubleProperty panning_scalar | ||
| def __init__(self, context, panner_strategy=PannerStrategy.DELEGATE, panning_scalar=0.0): | ||
@@ -722,4 +832,4 @@ cdef syz_Handle ctx = context._get_handle_checked(Context) | ||
| super().__init__(out) | ||
| self.panning_scalar = DoubleProperty(self, SYZ_P_PANNING_SCALAR) | ||
| panning_scalar = DoubleProperty(SYZ_P_PANNING_SCALAR) | ||
@@ -729,2 +839,7 @@ cdef class Source3D(Source): | ||
| cdef public DoubleProperty distance_ref, distance_max, rolloff, closeness_boost, closeness_boost_distance | ||
| cdef public Double3Property position | ||
| cdef public Double6Property orientation | ||
| cdef public IntProperty distance_model | ||
| def __init__(self, context, panner_strategy=PannerStrategy.DELEGATE, position=(0.0, 0.0, 0.0)): | ||
@@ -735,11 +850,11 @@ cdef syz_Handle ctx = context._get_handle_checked(Context) | ||
| super().__init__(out) | ||
| self.distance_model = enum_property(self, SYZ_P_DISTANCE_MODEL, lambda x: DistanceModel(x)) | ||
| self.distance_ref = DoubleProperty(self, SYZ_P_DISTANCE_REF) | ||
| self.distance_max = DoubleProperty(self, SYZ_P_DISTANCE_MAX) | ||
| self.rolloff = DoubleProperty(self, SYZ_P_ROLLOFF) | ||
| self.closeness_boost = DoubleProperty(self, SYZ_P_CLOSENESS_BOOST) | ||
| self.closeness_boost_distance = DoubleProperty(self, SYZ_P_CLOSENESS_BOOST_DISTANCE) | ||
| self.position = Double3Property(self, SYZ_P_POSITION) | ||
| self.orientation = Double6Property(self, SYZ_P_ORIENTATION) | ||
| distance_model = enum_property(SYZ_P_DISTANCE_MODEL, lambda x: DistanceModel(x)) | ||
| distance_ref = DoubleProperty(SYZ_P_DISTANCE_REF) | ||
| distance_max = DoubleProperty(SYZ_P_DISTANCE_MAX) | ||
| rolloff = DoubleProperty(SYZ_P_ROLLOFF) | ||
| closeness_boost = DoubleProperty(SYZ_P_CLOSENESS_BOOST) | ||
| closeness_boost_distance = DoubleProperty(SYZ_P_CLOSENESS_BOOST_DISTANCE) | ||
| position = Double3Property(SYZ_P_POSITION) | ||
| orientation = Double6Property(SYZ_P_ORIENTATION) | ||
@@ -854,3 +969,9 @@ cdef class Buffer(_BaseObject): | ||
| cdef class BufferGenerator(Generator): | ||
| cdef public IntProperty looping | ||
| cdef public ObjectProperty buffer | ||
| cdef public DoubleProperty playback_position | ||
| def __init__(self, context): | ||
@@ -860,8 +981,7 @@ cdef syz_Handle handle | ||
| super().__init__(handle) | ||
| self.buffer = ObjectProperty(self, SYZ_P_BUFFER, Buffer) | ||
| self.playback_position = DoubleProperty(self, SYZ_P_PLAYBACK_POSITION) | ||
| self.looping = IntProperty(self, SYZ_P_LOOPING, conv_in = int, conv_out = bool) | ||
| buffer = ObjectProperty(SYZ_P_BUFFER, Buffer) | ||
| playback_position = DoubleProperty(SYZ_P_PLAYBACK_POSITION) | ||
| looping = IntProperty(SYZ_P_LOOPING, conv_in = int, conv_out = bool) | ||
| class NoiseType(Enum): | ||
@@ -872,3 +992,6 @@ UNIFORM = SYZ_NOISE_TYPE_UNIFORM | ||
| cdef class NoiseGenerator(Generator): | ||
| cdef public IntProperty noise_type | ||
| def __init__(self, context, channels = 1): | ||
@@ -878,13 +1001,19 @@ cdef syz_Handle handle | ||
| super().__init__(handle) | ||
| self.noise_type = enum_property(self, SYZ_P_NOISE_TYPE, lambda x: NoiseType(x)) | ||
| noise_type = enum_property(SYZ_P_NOISE_TYPE, lambda x: NoiseType(x)) | ||
| cdef class GlobalEffect(_BaseObject): | ||
| cdef public BiquadProperty filter_input | ||
| cdef public DoubleProperty gain | ||
| def __init__(self, syz_Handle handle): | ||
| super().__init__(handle) | ||
| self.gain = DoubleProperty(self, SYZ_P_GAIN) | ||
| self.filter_input = BiquadProperty(self, SYZ_P_FILTER_INPUT) | ||
| cpdef reset(self): | ||
| _checked(syz_effectReset(self.handle)) | ||
| gain = DoubleProperty(SYZ_P_GAIN) | ||
| filter_input = BiquadProperty(SYZ_P_FILTER_INPUT) | ||
| cdef class EchoTapConfig: | ||
@@ -931,2 +1060,7 @@ """An echo tap. Passed to GlobalEcho.set_taps.""" | ||
| cdef class GlobalFdnReverb(GlobalEffect): | ||
| cdef public DoubleProperty mean_free_path, t60, late_reflections_lf_rolloff | ||
| cdef public late_reflections_lf_reference, late_reflections_hf_rolloff, late_reflections_hf_reference | ||
| cdef public late_reflections_diffusion, late_reflections_modulation_depth, late_reflections_modulation_frequency, late_reflections_delay | ||
| def __init__(self, context): | ||
@@ -936,12 +1070,99 @@ cdef syz_Handle handle | ||
| super().__init__(handle) | ||
| self.mean_free_path = DoubleProperty(self, SYZ_P_MEAN_FREE_PATH) | ||
| self.t60 = DoubleProperty(self, SYZ_P_T60) | ||
| self.late_reflections_lf_rolloff = DoubleProperty(self, SYZ_P_LATE_REFLECTIONS_LF_ROLLOFF) | ||
| self.late_reflections_lf_reference = DoubleProperty(self, SYZ_P_LATE_REFLECTIONS_LF_REFERENCE) | ||
| self.late_reflections_hf_rolloff = DoubleProperty(self, SYZ_P_LATE_REFLECTIONS_HF_ROLLOFF) | ||
| self.late_reflections_hf_reference = DoubleProperty(self, SYZ_P_LATE_REFLECTIONS_HF_REFERENCE) | ||
| self.late_reflections_diffusion = DoubleProperty(self, SYZ_P_LATE_REFLECTIONS_DIFFUSION) | ||
| self.late_reflections_modulation_depth = DoubleProperty(self, SYZ_P_LATE_REFLECTIONS_MODULATION_DEPTH) | ||
| self.late_reflections_modulation_frequency = DoubleProperty(self, SYZ_P_LATE_REFLECTIONS_MODULATION_FREQUENCY) | ||
| self.late_reflections_delay = DoubleProperty(self, SYZ_P_LATE_REFLECTIONS_DELAY) | ||
| mean_free_path = DoubleProperty(SYZ_P_MEAN_FREE_PATH) | ||
| t60 = DoubleProperty(SYZ_P_T60) | ||
| late_reflections_lf_rolloff = DoubleProperty(SYZ_P_LATE_REFLECTIONS_LF_ROLLOFF) | ||
| late_reflections_lf_reference = DoubleProperty(SYZ_P_LATE_REFLECTIONS_LF_REFERENCE) | ||
| late_reflections_hf_rolloff = DoubleProperty(SYZ_P_LATE_REFLECTIONS_HF_ROLLOFF) | ||
| late_reflections_hf_reference = DoubleProperty(SYZ_P_LATE_REFLECTIONS_HF_REFERENCE) | ||
| late_reflections_diffusion = DoubleProperty(SYZ_P_LATE_REFLECTIONS_DIFFUSION) | ||
| late_reflections_modulation_depth = DoubleProperty(SYZ_P_LATE_REFLECTIONS_MODULATION_DEPTH) | ||
| late_reflections_modulation_frequency = DoubleProperty(SYZ_P_LATE_REFLECTIONS_MODULATION_FREQUENCY) | ||
| late_reflections_delay = DoubleProperty(SYZ_P_LATE_REFLECTIONS_DELAY) | ||
| class InterpolationType(Enum): | ||
| NONE = SYZ_INTERPOLATION_TYPE_NONE | ||
| LINEAR = SYZ_INTERPOLATION_TYPE_LINEAR | ||
| cdef _convert_values_to_automation_array(obj): | ||
| """Convert either a single number or sequence of numbers to an array of 6 doubles, used internally by Synthizer AutomationPoints.""" | ||
| if isinstance(obj, abc.Sequence): | ||
| length = len(obj) | ||
| if length == 1: | ||
| return (obj[0], 0.0, 0.0, 0.0, 0.0, 0.0) | ||
| elif length == 3: | ||
| return (obj[0], obj[1], obj[2], 0.0, 0.0, 0.0) | ||
| elif length == 6: | ||
| return tuple(obj) | ||
| else: | ||
| raise ValueError("Automated property values require 1, 3, or 6 floats.") | ||
| return (float(obj), 0.0, 0.0, 0.0, 0.0, 0.0) | ||
| cdef class AutomationBatch(_BaseObject): | ||
| def __init__(self, context): | ||
| cdef syz_Handle handle | ||
| _checked(syz_createAutomationBatch(&handle, context._get_handle(), NULL, NULL)) | ||
| super().__init__(handle) | ||
| cpdef append_property(self, float time, _PropertyBase property, value, interpolation_type): | ||
| cdef syz_AutomationCommand command | ||
| cdef syz_AutomationAppendPropertyCommand appendPropertyCommand | ||
| cdef syz_AutomationPoint automationPoint | ||
| automationPoint.interpolation_type = interpolation_type.value | ||
| automationPoint.values = _convert_values_to_automation_array(value) | ||
| automationPoint.flags = 0 | ||
| appendPropertyCommand.property = property._get_property() | ||
| appendPropertyCommand.point = automationPoint | ||
| command.target = property._get_handle_checked() | ||
| command.time = time | ||
| command.type = SYZ_AUTOMATION_COMMAND_APPEND_PROPERTY | ||
| command.flags = 0 | ||
| command.params.append_to_property = appendPropertyCommand | ||
| _checked(syz_automationBatchAddCommands(self.handle, 1, &command)) | ||
| return self | ||
| cpdef clear_all_properties(self, _BaseObject target): | ||
| cdef syz_AutomationCommand command | ||
| command.target = target._get_handle() | ||
| command.type = SYZ_AUTOMATION_COMMAND_CLEAR_ALL_PROPERTIES | ||
| command.time = 0 | ||
| command.flags = 0 | ||
| _checked(syz_automationBatchAddCommands(self.handle, 1, &command)) | ||
| return self | ||
| cpdef clear_property(self, _PropertyBase property): | ||
| cdef syz_AutomationCommand command | ||
| cdef syz_AutomationClearPropertyCommand clearPropertyCommand | ||
| clearPropertyCommand.property = property._get_Property() | ||
| command.target = property._get_handle_checked() | ||
| command.type = SYZ_AUTOMATION_COMMAND_CLEAR_PROPERTY | ||
| command.time = 0.0 | ||
| command.flags = 0 | ||
| command.params.clear_property = clearPropertyCommand | ||
| _checked(syz_automationBatchAddCommands(self.handle, 1, &command)) | ||
| return self | ||
| cpdef clear_user_events(self, _BaseObject target): | ||
| cdef syz_AutomationCommand command | ||
| command.target = target._get_handle() | ||
| command.type = SYZ_AUTOMATION_COMMAND_CLEAR_EVENTS | ||
| command.time = 0.0 | ||
| command.flags = 0 | ||
| _checked(syz_automationBatchAddCommands(self.handle, 1, &command)) | ||
| return self | ||
| cpdef send_user_event(self, float time, _BaseObject target, unsigned long long param): | ||
| cdef syz_AutomationSendUserEventCommand sendUserEventCommand | ||
| cdef syz_AutomationCommand command | ||
| sendUserEventCommand.param = param | ||
| command.target = target._get_handle() | ||
| command.type = SYZ_AUTOMATION_COMMAND_SEND_USER_EVENT | ||
| command.time = time | ||
| command.flags = 0 | ||
| command.params.send_user_event = sendUserEventCommand | ||
| _checked(syz_automationBatchAddCommands(self.handle, 1, &command)) | ||
| return self | ||
| def execute(self): | ||
| _checked(syz_automationBatchExecute(self.handle)) |
Sorry, the diff of this file is too big to display
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
22725023
2.34%733
0.69%