{"id":48987104,"url":"https://github.com/zones-convolution/zones_convolver","last_synced_at":"2026-04-18T13:09:26.641Z","repository":{"id":227497031,"uuid":"744072279","full_name":"zones-convolution/zones_convolver","owner":"zones-convolution","description":"Convolution library used in the upcoming Zones Convolution plugin. Implements a non-uniform partitioned convolution (NUPC) scheme with modified Garcia optimal partitioning and time distributed transforms. This is able to run on a single thread without large spikes in load for variable block sizes without additional latency.","archived":false,"fork":false,"pushed_at":"2025-02-21T12:31:21.000Z","size":501,"stargazers_count":16,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-02-21T13:30:21.056Z","etag":null,"topics":["audio-processing","convolution","dsp","juce"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zones-convolution.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-01-16T15:13:29.000Z","updated_at":"2025-02-21T12:31:25.000Z","dependencies_parsed_at":"2024-08-23T14:16:27.741Z","dependency_job_id":"f7eab3d3-3989-446a-bbcd-a6c5b193f70f","html_url":"https://github.com/zones-convolution/zones_convolver","commit_stats":null,"previous_names":["zones-convolution/zones_convolver"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/zones-convolution/zones_convolver","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zones-convolution%2Fzones_convolver","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zones-convolution%2Fzones_convolver/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zones-convolution%2Fzones_convolver/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zones-convolution%2Fzones_convolver/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zones-convolution","download_url":"https://codeload.github.com/zones-convolution/zones_convolver/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zones-convolution%2Fzones_convolver/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31969958,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-18T00:39:45.007Z","status":"online","status_checked_at":"2026-04-18T02:00:07.018Z","response_time":103,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["audio-processing","convolution","dsp","juce"],"created_at":"2026-04-18T13:09:19.324Z","updated_at":"2026-04-18T13:09:26.632Z","avatar_url":"https://github.com/zones-convolution.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Contributors][contributors-shield]][contributors-url]\n[![Forks][forks-shield]][forks-url]\n[![Stargazers][stars-shield]][stars-url]\n[![Issues][issues-shield]][issues-url]\n[![MIT License][license-shield]][license-url]\n![Tests](https://img.shields.io/github/actions/workflow/status/zones-convolution/zones_convolver/zones_convolver_tests.yml?style=for-the-badge\u0026logo=github\u0026label=TESTS)\n\n![CMake](https://img.shields.io/badge/CMake-%23008FBA.svg?style=for-the-badge\u0026logo=cmake\u0026logoColor=white)\n![C++](https://img.shields.io/badge/c++-%2300599C.svg?style=for-the-badge\u0026logo=c%2B%2B\u0026logoColor=white)\n![JUCE Badge](https://img.shields.io/badge/JUCE-8DC63F?logo=juce\u0026logoColor=fff\u0026style=for-the-badge)\n\n---\n\n\u003cbr /\u003e\n\u003cdiv align=\"center\"\u003e\n  \u003ca href=\"https://github.com/zones-convolution/zones_convolver\"\u003e\n    \u003cimg src=\"resources/zones_icon.png\" alt=\"Logo\" width=\"80\" height=\"80\"\u003e\n  \u003c/a\u003e\n\n\u003ch3 align=\"center\"\u003eZones Convolver\u003c/h3\u003e\n\n  \u003cp align=\"center\"\u003e\n\n## !! Please note: This library has not seen production use. We **DO NOT RECOMMEND** using this yet. !!\n\nConvolution library used in the upcoming Zones Convolution plugin. Implements a non-uniform partitioned convolution (\nNUPC) scheme with modified Garcia optimal partitioning and time distributed transforms. This is able to run on a single\nthread without large spikes in load for variable block sizes without additional latency.\n\nCurrently, the library is in a first draft state and has not yet seen real use. We plan on improving the stability along\nwith user testing. We expect breaking changes will be introduced as the library evolves.\n\n\u003cbr /\u003e\n\u003ca href=\"https://github.com/zones-convolution/zones_convolver/issues\"\u003eReport Bug / Request Feature\u003c/a\u003e\n\u003c/p\u003e\n\u003c/div\u003e\n\n# Usage\n\nZones Convolver provides an interface that is able to load IRs offline and then smoothly fade them in/out in a (\nhopefully) thread-safe manner. Impulse responses should be loaded only from a thread where a short wait (used to copy\nthe IR) is acceptable. In the case of the Zones Convolver plugin we have a background thread that loads IRs. This is\nrequired as the convolution engine must allocate space for and copy the IR.\n\n## Convolution Engine\n\nThe ```ConvolutionEngine``` class inherits from ```juce::dsp::ProcessorBase``` exposing the ```prepare```, ```reset```\nand ```process``` methods. These should be called in their appropriate places. IRs can be loaded through\nthe ```LoadIR``` method that expects a ```juce::dsp::AudioBlock``` that will be copied.\n\n### Minimal Example\n\n```cpp\n#include \u003czones_convolver/zones_convolver.h\u003e\n\nclass MyPluginProcessor : juce::dsp::ProcessorBase\n{\nprivate:\n    juce::ThreadPool thread_pool_;\n    zones::ConvolutionEngine convolver_ {thread_pool_};\n\npublic:\n    ~MyPluginProcessor () override = default;\n    MyPluginProcessor (juce::dsp::AudioBlock\u003cconst float\u003e ir_to_load)\n    {\n        zones::Convolver::ConvolverSpec quad_spec {\n          .input_routing = {0, 1, 2, 3},\n          .output_routing = {0, 1, 2, 3},\n          .fade_strategy = zones::Convolver::FadeStrategy::kCrossfade\n        };\n        convolver_.LoadIR (ir_to_load, quad_spec);\n    }\n\n    void prepare (const juce::dsp::ProcessSpec \u0026 spec) override\n    {\n        convolver_.prepare (spec);\n    }\n\n    void process (const juce::dsp::ProcessContextReplacing\u003cfloat\u003e \u0026 replacing) override\n    {\n        convolver_.process (replacing);\n    }\n\n    void reset () override\n    {\n        convolver_.reset ();\n    }\n};\n```\n\nThough we suggest using the ```ConvolutionEngine``` class, it is also possible to directly use\nthe ```UniformPartitionedConvolver```\nand ```TimeDistributedNUPC``` without mechanisms for loading IRs safely across threads.\n\n# Installation\n\nZones Convolver is a JUCE module and can be consumed either through CMake or the Projucer. Installation steps are as\nfollows.\n\n## CMake\n\n![CMake](https://img.shields.io/badge/CMake-%23008FBA.svg?style=for-the-badge\u0026logo=cmake\u0026logoColor=white)\n\n```cmake\nadd_subdirectory(path/to/JUCE)\n\n# Make library available using FetchContent, CPM or simply a submodule\n\nadd_subdirectory(path/to/zones_convolver)\n\ntarget_link_libraries(juce_project\n        PRIVATE\n        zones_convolver\n)\n```\n\n## Projucer\n\n![JUCE Badge](https://img.shields.io/badge/Projucer-8DC63F?logo=juce\u0026logoColor=fff\u0026style=for-the-badge)\n\nDownload the library, then go to the module section in Projucer. Click the \"+\" and \"Add a module from a specified\nfolder...\". It can then be included in the header of your project.\n\n# Contributing\n\nWe would really welcome any contributions, feel free to open up a PR and we'll review it as soon as possible. The\nproject largely follows the Google C++ coding style and provides a ```.clang-format``` that should be used.\n\nCurrently on our roadmap,\n\n* Radix 4 decompositions and corresponding scheduling\n* Real only transforms\n* Micro-optimisations across the library\n* Multi-threaded NUPC implementation\n* Moving away from JUCE to be more flexible in other use cases\n\n# License\n\nDistributed under the MIT License. See `LICENSE` for more information.\n\n# Contact\n\nFeel free to get in touch,\n\n**Leon Paterson-Stephens** - leon@leonps.com\n\n**Micah Strange** - micahstrange16@gmail.com\n\n# Library Introduction\n\nA **PDF** version of this documentation is also provided\nhere, [Zones Convolver Documentation](resources/zones_convolver_documentation.pdf).\n\nUniform partitioned convolvers (UPC) are able to evenly distribute load as all operations needed for the entire\nconvolution including their required transforms, are completed in a single process call.\nFor larger filter sizes, it can be beneficial to employ non-uniform partitioned convolution (NUPC).\nIn these schemes, larger partitions are required to deliver the results of convolution at an interval of the partition's\nsize, not the block size/latency that the real-time callback is being run at. This can be problematic regarding\nload-distribution, particularly in the case of real-time audio plugins which are block based and conventionally limited\nto a single thread.\n\n## Schemes for NUPC\n\nA simple approach to implementing NUPC may be to process entire partitions in a single block at their respective\nintervals.\nHowever, this can be problematic when processing at lower latencies.\nThis is because, the entire computation of the forward and inverse transform along with the complex multiplications of\nthe entire partition are completed in a single frame.\nThis can yield dropouts in audio.\n\nThis is often compounded in schedules with multiple partitions as other partitions deadlines may also land at the same\ntime and now the computation of that entire partition's FDL, forward and inverse transform is also computed in that same\nframe. This is along with the computation of the uniform convolver running on every block to achieve minimal latency.\n\nOptimal partitioning schemes commonly include upper partition sizes of 65,536 samples for larger filters. These\npartitions use FFTs of twice the partition's size. The computation of the different partitions in the NUPC is not equal,\neach have their own FFT and FDL size along with deadlines for when each partition should deliver it's convolved samples.\n\nSelecting schemes with longer clearances gives more time for computation. Whilst this may not be as optimal as schemes\ncloser to Canonical partitioning, it allows distribution across frames. Alternative popular schemes, such as Gardener\nand Garcia partitioning can account for this.\n\n## Approaches to Distributing Load\n\nOne potential solution to realising NUPC in real-time is to ensure clearances of at least the partition size allowing\nthe effective size of each transform in each block to be equal.\nThe operating systems preemptive multi-threading can then be used to distribute the load of transforms and complex\nmultiplication over time while still running the UPC in the real-time thread.\nUsing the operating systems preemptive threading divides arbitrary schemes, without specific implementation effort.\nIn addition, the use of highly optimised FFT libraries such as FFTW is possible.\nThis scheme also has challenges to its implementation. For example, scheduling threads and their respective results\nsafely in real-time and ensuring the results of each partition are available at the right time.\n\nIn some contexts (such as web audio/mobile/embedded) the use of multiple threads may be problematic. Furthermore, it is\ncontentious whether it is appropriate in audio plugins.\n\nAn alternative approach to distribute the work is through time-distributed transforms and splitting the complex\nmultiplications. This is the approach used by this library.\nThe premise of this is that transforms can be decomposed into smaller independent sub-transforms that can then be\ncomputed independently across multiple process calls.\n\nDecomposing to achieve a well-balanced work load is dependent on a number of factors, which is discussed below.\n\n---\n\n# Implementation Detail\n\nThis section will present this libraries approach to a time-distributed NUPC. Each time-distributed NUPC is composed of\na single UPC processing at the block size and multiple time-distributed UPCs (TDUPCs). Each TDUPC can be thought of as\nan independent UPC operating at it's own block size, processing a given segment of the filter. The top level of the\nconvolver is responsible for sequencing results of all the TDUPCs.\n\nTDUPCs are given data as it becomes available, at the rate of process calls. However, are internally processed in three\nstages. Stages and phases are used to help simplify decomposition and transform scheduling. This is a similar approach\nto that taken by the RTConvolve library, however is able to accommodate varying partition sizes.\n\n## Stages / Phases\n\nEach stage is responsible for different types of work. Stages are promoted when a partition size worth of samples are\ncollected. Each stage operates over a number of phases directly corresponding to process calls. Stages contain\n$(partition size / block size)$ number of phases. For example, a TDUPC operating at 16 times the block size (16B) has\n16 phases per stage.\nDuring Stage A, samples are collected and decomposed immediately as they become available. Next, in Stage B, the\nsub-transforms and complex multiplications of the frequency-domain delay line (FDL) are performed. Finally, in Stage C,\nthe inverse decompositions are performed before delivering samples. Intuitively, all phases must be completed before a\nstage is promoted. However, all three stages are occurring concurrently every phase, acting on different buffers.\n\nThe number of phases is fixed at the partition size due to a number of factors. For small clearances it is required to\nwait until a partition size worth of samples is collected. Though the decompositions can be performed across all phases,\nstage B still delivers at intervals of the partition size, however can't distribute it's work across all phases as it's\nresult is required in stage C at an earlier point in time and is then left waiting for samples. Whilst large clearances\ncan allow distribution of work across more phases, convolved samples are still delivered at an interval of the partition\nsize therefore within one TDUPC there will occur overlapping work. In theory this could also be balanced however the\nsame result is achieved applying a delay to the output. Considering this, only schemes with clearances greater than or\nequal to the partition size are used.\n\n---\n\n## Stage A/C\n\nIn stage A, incoming samples are immediately placed into the stage A buffer where forward decompositions can be started\nimmediately, in place. This is only possible using decimation in frequency (DIF) decompositions as the first stage of\nthe butterfly corresponds to the first sample and the $(N/2)^{th}$ sample where the value is known to be zero, due to\nthe zero padding and 2B transform size.\n\nHalf way through the decompositions, when the first level decomposition is completed, there are sufficient samples to\nprocess the next level of the butterfly subdividing the transform further.\nThis decomposition scheme can continue until the transform is completed.\n\nInverse decomposition's are performed in Stage C, and are a mirrored version of the forward decomposition's. The cost of\ndecomposition's in the first phase is the highest and decreases toward the end, this has a benefit of balancing the\nuneven load of the forward decomposition's. Similarly to the forward decomposition's, the inverse schedule also delivers\nsamples in a just in time basis.\n\nIncreasing the depth of decomposition begins to yield larger spikes of load at the ends of the decomposition schedule.\nThis can be seen in Figure 1.\nDecomposition work is not free as it involves computing stages of the butterfly just as the FFT does. Furthermore, it\ncan't make certain optimisations that fully formed FFTs can and therefore should be limited to certain extents as will\nbe discussed.\n\n\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"resources/8_phase_decomposition.jpg\" alt=\"fig:8_phase_decomposition\" width=\"100%\"\u003e\n  \u003ch4 align=\"center\"\u003eFig 1: Maximally decomposed 8 Phase Schedule\u003c/h4\u003e\n\u003c/div\u003e\n\n### Decomposition Depth\n\nFigure 1 depicts a maximally decomposed 8 phase decomposition schedule. The size of the\ntransforms are 16B and are decomposed three times across 8 phases. This yields 8 sub transforms of 2B. In this case,\ndecomposing this far is sub-optimal as the number of resulting transforms (forward and inverse) is double the number of\nphases. It would be more optimal to decompose twice leaving 4 sub transforms of 4B allowing 4 forward transforms in the\nfirst 4 phases followed by the remaining 4 inverse transforms in the last 4 phases.\n\nDecomposition is continued until the number of sub-FFTs equals half the number of phases in the schedule. This is to\nallow for the cost of forward and inverse transforms to be equal across all phases.\n\n---\n\n## Stage B\n\nOnce stage B is reached, the initial large transforms will be composed of several smaller sub-transforms that can now be\ndistributed across the available phases of stage B.\nThe number of transforms is determined by the number of decompositions performed in the previous stage. However the\nnumber of complex multiplications is always constant for a given partition size and FDL length ($2*partition size*\nfdl size$).\n\nThe aim of FFT scheduling is to distribute the total work as evenly as possible across the total phases. The number of\ndecompositions is chosen such that ideally there is a single transform in each phase.\nThe number of channels effects the depth of decomposition and where transforms are placed in the schedule.\n\n### Mono distribution\n\nFor a single channel, the sub FFTs and IFFTs are performed on alternate phases, and the complex multiplications are\nspread evenly across every phase.\n\n\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"resources/simple_fft_schedule_example.jpg\" alt=\"fig:simple_fft_schedule_example\" width=\"100%\"\u003e\n  \u003ch4 align=\"center\"\u003eFig 2: 1 channel distribution for a 4B partition\u003c/h4\u003e\n\u003c/div\u003e\n\nThis arrangement allows a perfect distribution and is simple to realise.\nFor a mono distribution the decomposition depth is as follows\n$decomposition depth = log_{2}(num phases / 2)$\nThis results in a number of sub transforms equal to the number of phases.\n\n### Multi-channel distribution\n\nFor multiple channels, savings can be made on the amount of work by reducing the number of decompositions, and\ninterleaving work between channels. For a power of 2 channel count each channel equally occupies less phases. This can\nbe expanded for other channel counts by finding the highest power of 2 recursively.\n\n\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"resources/4_channel_fft_schedule.jpg\" alt=\"fig:4_channel_fft_schedule\" width=\"100%\"\u003e\n  \u003ch4 align=\"center\"\u003eFig 3: 4 channel interleaving for an 8B partition, where the number of transforms per channel is reduced from 8 to 2\u003c/h4\u003e\n\u003c/div\u003e\n\n\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"resources/7_channel_fft_schedule.jpg\" alt=\"fig:7_channel_fft_schedule\" width=\"100%\"\u003e\n  \u003ch4 align=\"center\"\u003eFig 4: 7 channel interleaving for an 8B partition. The channels are grouped in 4,2,1\u003c/h4\u003e\n\u003c/div\u003e\n\n\nChannels are able to share the frequency domain filter even with different FFT schedules as the same frequency domain\nrepresentation is achieved irrespective of the sub-transform size.\nAn area for further research may be applying a similar scheme to interleave multiple TDUPCs.\n\n---\n\n## Handling Block Sizes\n\nEach convolver must be initially prepared with a block size. This is used for finding the optimal partition scheme and\nbreaking up the filter into different sized partitions.\n\nThe handling of block sizes across DAWs varies greatly. In the model of audio plugins a maximum block size is provided,\nwhich is often the block size that is processed. However, smaller block sizes can be presented and this case should be\nexpected.\nThis presents a problem for a convolution engine set up to run at a fixed block size.\nOne approach would be to add latency to allow input samples to be buffered up to the maximum block size. However, this\nmeans that DAWs not processing at the maximum block size do not achieve a minimum latency.\n% Furthermore, this approach is not flexible to incoming block sizes frequently changing in size.\nThe approach taken in this library solves this issue. A similar approach is also found in JUCE's convolver. In this\nimplementation, the convolution engine is able to run at a fixed internal block size and can handle both larger and\nsmaller processing block sizes.\n\n### Implementation\n\nThe incoming block of data is immediately convolved with the first block within the FDL of the UPC regardless of it's\nprocessing block size. This is achieved through zero padding the input up to the internal block size of the UPC. The\ninputs are saved, and the results of convolution are added directly to the output. Subsequent process calls continue to\nfill the input buffer and outputs are read with an offset in order to align in time. When the internal block size of the\nUPC is reached a complete transform is added to the FDL. The entire FDL is processed at intervals of the block size and\nthen stored. When block sizes are presented that are greater than the internal block size, the UPC is effectively\nprocessed using multiple subdivisions of the incoming block.\nA similar approach is used by the NUPC to allow the TDUPCs to function as normal. The TDUPCs are only processed at\nintervals of the internal block size.\n\n### Limitations\n\nThis approach can increase workload and decrease distribution of the load of the convolver. For the UPC, a transform of\ntwo times the internal block size is computed every process call, regardless of the processing block size. For example,\nif the incoming data is half the internal block size, the total FFT work doubles compared to preparing the convolution\nengine to match the incoming block size.\nFor the TDUPCs, the workload may not be distributed evenly every process call. When the incoming data is half the\ninternal block size, a previously well distributed TDUPC will now only do work every other call and has less time to\nperform it's computation.\n\n---\n\n## Partition Schemes\n\nA modified Garcia partitioning algorithm is used to find optimum layouts for each block size and filter length. These\nschemes have been pre-computed and embedded in the library. This is necessary as computing Garcia schemes when presented\nwith specific configurations would be impractical, especially for large filters. This allows for an approximation of the\nGarcia schemes to be found. This approach provides flexibility to modify schemes in the future with developments in\npartitioning algorithms.\n\nEmbedded schemes are first searched to find the nearest available scheme. Where the filter is longer than the closest\npre-computed scheme, additional partitions are added to the largest partition size. If the filter is shorter, partitions\nare removed, including removing entire TDUPCs if necessary. A future improvement for filter lengths above the largest\nsaved scheme would add additional TDUPCs following Gardener partitioning rules.\n\nThe modified Garcia partition scheme has been implemented in python along with a cpp generator used to embed the\npartitioning results.\n\n# Acknowledgments / References\n\n\u003c!--- // @formatter:off ---\u003e\n\n* **Frank Wefers** - Partitioned convolution algorithms for real-time auralization\n* **Jeffrey R. Hurchalla** - A Time Distributed FFT for Efficient Low Latency Convolution\n* **Eric Battenberg, Rimas Avizienis** - IMPLEMENTING REAL-TIME PARTITIONED CONVOLUTION ALGORITHMS ON CONVENTIONAL\n  OPERATING SYSTEMS\n* **Guillermo García** - Optimal Filter Partition for Efficient Convolution with Short Input/Output Delay\n* **Graham Barab** - [RTCONVOLVE](https://github.com/grahman/RTConvolve)\n* **JUCE** - [juce::dsp::Convolution](https://docs.juce.com/master/classdsp_1_1Convolution.html)\n* **Jan Wilczek** - [Fast Convolution: FFT-based, Overlap-Add, Overlap-Save, Partitioned](https://thewolfsound.com/fast-convolution-fft-based-overlap-add-overlap-save-partitioned/#the-convolution-series)\n* [Readme Boilerplate](https://github.com/othneildrew/Best-README-Template)\n\n\u003c!--- // @formatter:on ---\u003e\n\n[contributors-shield]: https://img.shields.io/github/contributors/zones-convolution/zones_convolver.svg?style=for-the-badge\n\n[contributors-url]: https://github.com/zones-convolution/zones_convolver/graphs/contributors\n\n[forks-shield]: https://img.shields.io/github/forks/zones-convolution/zones_convolver.svg?style=for-the-badge\n\n[forks-url]: https://github.com/zones-convolution/zones_convolver/network/members\n\n[stars-shield]: https://img.shields.io/github/stars/zones-convolution/zones_convolver.svg?style=for-the-badge\n\n[stars-url]: https://github.com/zones-convolution/zones_convolver/stargazers\n\n[issues-shield]: https://img.shields.io/github/issues/zones-convolution/zones_convolver.svg?style=for-the-badge\n\n[issues-url]: https://github.com/zones-convolution/zones_convolver/issues\n\n[license-shield]: https://img.shields.io/github/license/zones-convolution/zones_convolver.svg?style=for-the-badge\n\n[license-url]: https://github.com/zones-convolution/zones_convolver/blob/main/LICENSE\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzones-convolution%2Fzones_convolver","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzones-convolution%2Fzones_convolver","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzones-convolution%2Fzones_convolver/lists"}