From dfa87f0026577eafe86ac6de9ce3e3cddab14749 Mon Sep 17 00:00:00 2001 From: Pierre Alexandre Tremblay Date: Sat, 27 Oct 2018 19:45:09 +0100 Subject: [PATCH 001/110] BufNoveltySlice: new help file with filtersize --- .../Classes/FluidBufNoveltySlice.schelp | 41 ++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/release-packaging/HelpSource/Classes/FluidBufNoveltySlice.schelp b/release-packaging/HelpSource/Classes/FluidBufNoveltySlice.schelp index 777b609..c7a4dde 100644 --- a/release-packaging/HelpSource/Classes/FluidBufNoveltySlice.schelp +++ b/release-packaging/HelpSource/Classes/FluidBufNoveltySlice.schelp @@ -40,7 +40,10 @@ ARGUMENT:: kernelSize The granularity of the window in which the algorithm looks for change, in samples. A small number will be sensitive to short term changes, and a large number should look for long term changes. ARGUMENT:: thresh - The normalised threshold, between 0 an 1, to consider a peak as a sinusoidal component from the in the novelty curve. + The normalised threshold, between 0 an 1, on the novelty curve to consider it a segmentation point. + +ARGUMENT:: filterSize + The size of a smoothing filter that is applied on the novelty curve. A larger filter filter size allows for cleaner cuts on very sharp changes. ARGUMENT:: winSize The window size. As novelty estimation relies on spectral frames, we need to decide what precision we give it spectrally and temporally, in line with Gabor Uncertainty principles. http://www.subsurfwiki.org/wiki/Gabor_uncertainty @@ -91,3 +94,39 @@ c.query; }.play; ) :: + +STRONG::Examples of the impact of the filterSize:: + + CODE:: +// load some buffers +( +b = Buffer.read(s,File.realpath(FluidBufNoveltySlice.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-AaS-AcousticStrums-M.wav"); +c = Buffer.new(s); +) + +// process with a given filterSize +FluidBufNoveltySlice.process(s,b.bufnum, transBufNum: c.bufnum, kernelSize:31, thresh:0.35, filterSize:0) + +//check the number of slices: it is the number of frames in the transBuf minus the boundary index. +c.query; + +//play slice number 2 +( +{ + BufRd.ar(1, b.bufnum, + Line.ar( + BufRd.kr(1, c.bufnum, DC.kr(2), 0, 1), + BufRd.kr(1, c.bufnum, DC.kr(3), 0, 1), + (BufRd.kr(1, c.bufnum, DC.kr(3)) - BufRd.kr(1, c.bufnum, DC.kr(2), 0, 1) + 1) / s.sampleRate), + 0,1); +}.play; +) + +// change the filterSize in the code above to 4. Then to 8. Listen in between to the differences. + +// What's happening? In the first instance (filterSize = 0), the novelty line is jittery and therefore overtriggers on the arpegiated guitar. We also can hear attacks at the end of the segment. Setting the threshold higher (like in the 'Basic Example' pane) misses some more subtle variations. + +// So in the second settings (filterSize = 4), we smooth the novelty line a little, which allows us to catch small differences that are not jittery. It also corrects the ending cutting by the same trick: the averaging of the sharp pick is sliding up, crossing the threshold slightly earlier. + +// If we smooth too much, like the third settings (filterSize = 8), we start to loose precision. Have fun with different values of theshold then will allow you to find the perfect segment for your signal. +:: \ No newline at end of file From 088f900f708d6fff32417c0fd98a60fdec7cafc9 Mon Sep 17 00:00:00 2001 From: Owen Green Date: Thu, 1 Nov 2018 11:37:03 +0000 Subject: [PATCH 002/110] release-packaging/Classes/FluidNMFMatch.sc: Class definition changed rank -> maxrank src/FluidNMFMatch/FluidNMFMatch.cpp: Use maxrank, check for actual rank in next() src/FluidNMFMatch/test.scd: test explicitly with maxrank --- release-packaging/Classes/FluidNMFMatch.sc | 4 ++-- src/FluidNMFMatch/FluidNMFMatch.cpp | 25 ++++++++++++++++------ src/FluidNMFMatch/test.scd | 2 +- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/release-packaging/Classes/FluidNMFMatch.sc b/release-packaging/Classes/FluidNMFMatch.sc index c34b730..cb4fa92 100644 --- a/release-packaging/Classes/FluidNMFMatch.sc +++ b/release-packaging/Classes/FluidNMFMatch.sc @@ -1,7 +1,7 @@ FluidNMFMatch : MultiOutUGen { - *kr { arg in = 0, dictBufNum, rank = 1, nIter = 10, winSize = 1024, hopSize = 256, fftSize = -1; - ^this.multiNew('control', in, dictBufNum, rank, nIter, winSize, hopSize, fftSize); + *kr { arg in = 0, dictBufNum, maxrank = 1, nIter = 10, winSize = 1024, hopSize = 256, fftSize = -1; + ^this.multiNew('control', in, dictBufNum, maxrank, nIter, winSize, hopSize, fftSize); } init {arg ...theInputs; diff --git a/src/FluidNMFMatch/FluidNMFMatch.cpp b/src/FluidNMFMatch/FluidNMFMatch.cpp index 7a950a2..048015a 100644 --- a/src/FluidNMFMatch/FluidNMFMatch.cpp +++ b/src/FluidNMFMatch/FluidNMFMatch.cpp @@ -45,7 +45,7 @@ namespace nmf{ return; } - mRank = parameter::lookupParam("rank", mClient->getParams()).getLong(); + mMaxRank = parameter::lookupParam("maxrank", mClient->getParams()).getLong(); mClient->set_host_buffer_size(bufferSize()); @@ -53,8 +53,8 @@ namespace nmf{ inputSignals[0] = SignalPointer(new AudioSignalWrapper()); - outputSignals.resize(mRank); - for(size_t i = 0; i < mRank; ++i) + outputSignals.resize(mMaxRank); + for(size_t i = 0; i < mMaxRank; ++i) outputSignals[i].reset(new Client::ScalarSignal()); mCalcFunc = make_calc_function(); @@ -106,21 +106,32 @@ namespace nmf{ void next(int numsamples) { + auto filters = parameter::lookupParam("filterbuf", mClient->getParams()).getBuffer(); + + if(!filters) return; + setParams(false); const float* input = zin(0); const float inscalar = in0(0); inputSignals[0]->set(const_cast(input), inscalar); - for(size_t i = 0; i < mRank; ++i) + + for(size_t i = 0; i < mMaxRank; ++i) outputSignals[i]->set(out(i),out0(i)); - mClient->do_process_noOLA(inputSignals.begin(),inputSignals.end(), outputSignals.begin(), outputSignals.end(), mWorld->mFullRate.mBufLength ,1,mRank); - for(size_t i = 0; i < mRank; ++i) + mClient->do_process_noOLA(inputSignals.begin(),inputSignals.end(), outputSignals.begin(), outputSignals.end(), mWorld->mFullRate.mBufLength ,1,mMaxRank); + + parameter::BufferAdaptor::Access buf(filters); + long actualRank = buf.numChans(); + for(size_t i = 0; i < actualRank; ++i) + out0(i) = outputSignals[i]->next(); + + for(size_t i = actualRank; i < mMaxRank; ++i) out0(i) = outputSignals[i]->next(); } - size_t mRank; + size_t mMaxRank; ClientPointer mClient; SignalArray<1> inputSignals; SignalVector outputSignals; diff --git a/src/FluidNMFMatch/test.scd b/src/FluidNMFMatch/test.scd index 7ec2850..b7173e4 100644 --- a/src/FluidNMFMatch/test.scd +++ b/src/FluidNMFMatch/test.scd @@ -37,5 +37,5 @@ Routine{ }.play; ) -{DelayN.ar(PlayBuf.ar(1,b.bufnum),0.1,1024/44100, FluidNMFMatch.kr(PlayBuf.ar(1,b.bufnum),e.bufnum,2))}.play +{DelayN.ar(PlayBuf.ar(1,b.bufnum),0.1,1024/44100, FluidNMFMatch.kr(PlayBuf.ar(1,b.bufnum),e.bufnum,maxrank:2))}.play From ebe891d16b6330a163a1fcaf1f1e778203b6e122 Mon Sep 17 00:00:00 2001 From: Pierre Alexandre Tremblay Date: Thu, 1 Nov 2018 13:41:58 +0000 Subject: [PATCH 003/110] FluidNMFMatch declaration and attempt to Englicise --- release-packaging/Classes/FluidNMFMatch.sc | 4 ++-- release-packaging/HelpSource/Classes/FluidNMFMatch.schelp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/release-packaging/Classes/FluidNMFMatch.sc b/release-packaging/Classes/FluidNMFMatch.sc index cb4fa92..59228ce 100644 --- a/release-packaging/Classes/FluidNMFMatch.sc +++ b/release-packaging/Classes/FluidNMFMatch.sc @@ -1,7 +1,7 @@ FluidNMFMatch : MultiOutUGen { - *kr { arg in = 0, dictBufNum, maxrank = 1, nIter = 10, winSize = 1024, hopSize = 256, fftSize = -1; - ^this.multiNew('control', in, dictBufNum, maxrank, nIter, winSize, hopSize, fftSize); + *kr { arg in = 0, dictBufNum, maxRank = 1, nIter = 10, winSize = 1024, hopSize = 256, fftSize = -1; + ^this.multiNew('control', in, dictBufNum, maxRank, nIter, winSize, hopSize, fftSize); } init {arg ...theInputs; diff --git a/release-packaging/HelpSource/Classes/FluidNMFMatch.schelp b/release-packaging/HelpSource/Classes/FluidNMFMatch.schelp index a08e9cc..5b5838e 100644 --- a/release-packaging/HelpSource/Classes/FluidNMFMatch.schelp +++ b/release-packaging/HelpSource/Classes/FluidNMFMatch.schelp @@ -1,10 +1,10 @@ TITLE:: FluidNMFMatch -SUMMARY:: Real-Time Non-Negative Matrix Factorisation on Buffered Dictionaries +SUMMARY:: Real-Time Non-Negative Matrix Factorisation with Fixed Dictionaries CATEGORIES:: Libraries>FluidDecomposition RELATED:: Guides/FluCoMa, Guides/FluidDecomposition, Classes/FluidBufNMF DESCRIPTION:: -The FluidBufNMF object provides the activation (linked to amplitude) for each pre-defined dictionaries (similar to spectra) predefined in a buffer. These dictionaries would have usually be computed through an offline Non-Negative Matrix Factorisation (NMF) footnote:: Lee, Daniel D., and H. Sebastian Seung. 1999. ‘Learning the Parts of Objects by Non-Negative Matrix Factorization’. Nature 401 (6755): 788–91. https://doi.org/10.1038/44565 :: with the link::Classes/FluidBufNMF:: UGen. NMF has been a popular technique in signal processing research for things like source separation and transcription footnote:: Smaragdis and Brown, Non-Negative Matrix Factorization for Polyphonic Music Transcription.::, although its creative potential is so far relatively unexplored. +The FluidNMFMatch object provides the activation (linked to amplitude) for each pre-defined dictionaries (similar to spectra) predefined in a buffer. These dictionaries would have usually be computed through an offline Non-Negative Matrix Factorisation (NMF) footnote:: Lee, Daniel D., and H. Sebastian Seung. 1999. ‘Learning the Parts of Objects by Non-Negative Matrix Factorization’. Nature 401 (6755): 788–91. https://doi.org/10.1038/44565 :: with the link::Classes/FluidBufNMF:: UGen. NMF has been a popular technique in signal processing research for things like source separation and transcription footnote:: Smaragdis and Brown, Non-Negative Matrix Factorization for Polyphonic Music Transcription.::, although its creative potential is so far relatively unexplored. The algorithm takes a buffer in which provides a spectral definition of a number of components, determined by the rank argument and the dictionary buffer channel count. It works iteratively, by trying to find a combination of amplitudes ('activations') that yield the original magnitude spectrogram of the audio input when added together. By and large, there is no unique answer to this question (i.e. there are different ways of accounting for an evolving spectrum in terms of some set of templates and envelopes). In its basic form, NMF is a form of unsupervised learning: it starts with some random data and then converges towards something that minimizes the distance between its generated data and the original:it tends to converge very quickly at first and then level out. Fewer iterations mean less processing, but also less predictable results. From 464d3cfc5ac4ad1f50f2e0aeec78998f6adce7f9 Mon Sep 17 00:00:00 2001 From: Owen Green Date: Thu, 1 Nov 2018 16:58:19 +0000 Subject: [PATCH 004/110] release-packaging/HelpSource/Classes/FluidNMFMatch.schelp: Changes to description text --- release-packaging/HelpSource/Classes/FluidNMFMatch.schelp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/release-packaging/HelpSource/Classes/FluidNMFMatch.schelp b/release-packaging/HelpSource/Classes/FluidNMFMatch.schelp index 5b5838e..67dda8c 100644 --- a/release-packaging/HelpSource/Classes/FluidNMFMatch.schelp +++ b/release-packaging/HelpSource/Classes/FluidNMFMatch.schelp @@ -4,9 +4,11 @@ CATEGORIES:: Libraries>FluidDecomposition RELATED:: Guides/FluCoMa, Guides/FluidDecomposition, Classes/FluidBufNMF DESCRIPTION:: -The FluidNMFMatch object provides the activation (linked to amplitude) for each pre-defined dictionaries (similar to spectra) predefined in a buffer. These dictionaries would have usually be computed through an offline Non-Negative Matrix Factorisation (NMF) footnote:: Lee, Daniel D., and H. Sebastian Seung. 1999. ‘Learning the Parts of Objects by Non-Negative Matrix Factorization’. Nature 401 (6755): 788–91. https://doi.org/10.1038/44565 :: with the link::Classes/FluidBufNMF:: UGen. NMF has been a popular technique in signal processing research for things like source separation and transcription footnote:: Smaragdis and Brown, Non-Negative Matrix Factorization for Polyphonic Music Transcription.::, although its creative potential is so far relatively unexplored. +The FluidNMFMatch object matches an incoming audio signal against a set of spectral templates using an slimmed-down version of Nonnegative Matrix Factorisation (NMF)footnote:: Lee, Daniel D., and H. Sebastian Seung. 1999. ‘Learning the Parts of Objects by Non-Negative Matrix Factorization’. Nature 401 (6755): 788–91. https://doi.org/10.1038/44565. -The algorithm takes a buffer in which provides a spectral definition of a number of components, determined by the rank argument and the dictionary buffer channel count. It works iteratively, by trying to find a combination of amplitudes ('activations') that yield the original magnitude spectrogram of the audio input when added together. By and large, there is no unique answer to this question (i.e. there are different ways of accounting for an evolving spectrum in terms of some set of templates and envelopes). In its basic form, NMF is a form of unsupervised learning: it starts with some random data and then converges towards something that minimizes the distance between its generated data and the original:it tends to converge very quickly at first and then level out. Fewer iterations mean less processing, but also less predictable results. +It outputs at kr the degree of detected match for each template (the activation amount, in NMF-terms). The spectral templates are presumed to have been produced by the offline NMF process (link::Classes/FluidBufNMF::), and must be the correct size with respect to the FFT settings being used (FFT size / 2 + 1 frames long). The rank of the decomposition is determined by the number of channels in the supplied buffer of templates, up to a maximum set by the ::maxrank:: parameter. + +NMF has been a popular technique in signal processing research for things like source separation and transcription footnote:: Smaragdis and Brown, Non-Negative Matrix Factorization for Polyphonic Music Transcription.::, although its creative potential is so far relatively unexplored. It works iteratively, by trying to find a combination of amplitudes ('activations') that yield the original magnitude spectrogram of the audio input when added together. By and large, there is no unique answer to this question (i.e. there are different ways of accounting for an evolving spectrum in terms of some set of templates and envelopes). In its basic form, NMF is a form of unsupervised learning: it starts with some random data and then converges towards something that minimizes the distance between its generated data and the original:it tends to converge very quickly at first and then level out. Fewer iterations mean less processing, but also less predictable results. The whole process can be related to a channel vocoder where, instead of fixed bandpass filters, we get more complex filter shapes and the activations correspond to channel envelopes. From 7fdd2c787f11e9bc61eda1712b549cb001402a3b Mon Sep 17 00:00:00 2001 From: Owen Green Date: Thu, 1 Nov 2018 17:16:44 +0000 Subject: [PATCH 005/110] release-packaging/HelpSource/Classes/FluidNMFMatch.schelp: Changes to argument descriptions. --- .../HelpSource/Classes/FluidNMFMatch.schelp | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/release-packaging/HelpSource/Classes/FluidNMFMatch.schelp b/release-packaging/HelpSource/Classes/FluidNMFMatch.schelp index 67dda8c..b67def6 100644 --- a/release-packaging/HelpSource/Classes/FluidNMFMatch.schelp +++ b/release-packaging/HelpSource/Classes/FluidNMFMatch.schelp @@ -22,31 +22,31 @@ This was made possible thanks to the FluCoMa project ( http://www.flucoma.org/ CLASSMETHODS:: METHOD:: kr -The real-time processing method. It takes an audio or control input, and will yield a control stream in the form of a multichannel array of size STRONG::rank::. +The real-time processing method. It takes an audio or control input, and will yield a control stream in the form of a multichannel array of size STRONG::maxrank::. If the dictionary buffer has fewer than maxrank channels, the remaining outputs will be zeroed. ARGUMENT:: in -The input to the factorisation process. +The signal input to the factorisation process. ARGUMENT:: dictBufNum - The index of the buffer where the different dictionaries will be matched against. Dictionaries must be STRONG::(fft size / 2) + 1:: frames and STRONG::rank:: channels + The server index of the buffer containing the different dictionaries that the input signal will be matched against. Dictionaries must be STRONG::(fft size / 2) + 1:: frames. If the buffer has more than STRONG::(maxrank) channels, the excess will be ignored. -ARGUMENT:: rank - The number of elements the NMF algorithm will try to divide the spectrogram of the source in. This should match the number of channels of the dictBuf defined above. +ARGUMENT::maxrank + The maximum number of elements the NMF algorithm will try to divide the spectrogram of the source in. This dictates the number of output channelsfor the ugen. ARGUMENT:: nIter - The NMF process is iterative, trying to converge to the smallest error in its factorisation. The number of iterations will decide how many times it tries to adjust its estimates. Higher numbers here will be more CPU expensive, lower numbers will be more unpredictable in quality. + The NMF process is iterative, trying to converge to the smallest error in its factorisation. The number of iterations will decide how many times it tries to adjust its estimates. Higher numbers here will be more CPU intensive, lower numbers will be more unpredictable in quality. ARGUMENT:: winSize - The window size. As NMF relies on spectral frames, we need to decide what precision we give it spectrally and temporally, in line with Gabor Uncertainty principles. http://www.subsurfwiki.org/wiki/Gabor_uncertainty + The number of samples that are analysed at a time. A lower number yields greater temporal resolution, at the expense of spectral resoultion, and vice-versa. ARGUMENT:: hopSize - The window hope size. As NMF relies on spectral frames, we need to move the window forward. It can be any size but low overlap will create audible artefacts. + The window hope size. As NMF relies on spectral frames, we need to move the window forward. It can be any size but low overlap will create audible artefacts. Default = winSize / 2 ARGUMENT:: fftSize - The inner FFT/IFFT size. It should be at least 4 samples long, at least the size of the window, and a power of 2. Making it larger allows an oversampling of the spectral precision. + The FFT/IFFT size. It should be at least 4 samples long, at least the size of the window, and a power of 2. Making it larger allows an oversampling of the spectral precision. Default = winSize returns:: - A multichannel array, giving for each dictionary the activation value. + A multichannel kr output, giving for each dictionary component the activation amount. EXAMPLES:: From 8dd5a23777ce12a37f9177c8c5e7231823c2c00a Mon Sep 17 00:00:00 2001 From: Pierre Alexandre Tremblay Date: Mon, 5 Nov 2018 20:14:32 +0000 Subject: [PATCH 006/110] BufNMF error in the helpfile, plus beginning of an example for nmfmatch (with a bug find) --- .../HelpSource/Classes/FluidBufNMF.schelp | 5 +- .../HelpSource/Classes/FluidNMFMatch.schelp | 79 ++++++++++++++++--- 2 files changed, 70 insertions(+), 14 deletions(-) diff --git a/release-packaging/HelpSource/Classes/FluidBufNMF.schelp b/release-packaging/HelpSource/Classes/FluidBufNMF.schelp index 36d3dc0..07b7b9b 100644 --- a/release-packaging/HelpSource/Classes/FluidBufNMF.schelp +++ b/release-packaging/HelpSource/Classes/FluidBufNMF.schelp @@ -31,10 +31,7 @@ The whole process can be related to a channel vocoder where, instead of fixed ba More information on possible musicianly uses of NMF are availabe in LINK::Guides/FluCoMa:: overview file. FluidBufNMF is part of the Fluid Decomposition Toolkit of the FluCoMa project. footnote:: -This was made possible thanks to the FluCoMa project ( http://www.flucoma.org/ ) funded by the European Research Council ( https://erc.europa.eu/ ) under the European Union’s Horizon 2020 research and innovation programme (grant agreement No 725899). -:: - - +This was made possible thanks to the FluCoMa project ( http://www.flucoma.org/ ) funded by the European Research Council ( https://erc.europa.eu/ ) under the European Union’s Horizon 2020 research and innovation programme (grant agreement No 725899). :: CLASSMETHODS:: diff --git a/release-packaging/HelpSource/Classes/FluidNMFMatch.schelp b/release-packaging/HelpSource/Classes/FluidNMFMatch.schelp index b67def6..6e792ba 100644 --- a/release-packaging/HelpSource/Classes/FluidNMFMatch.schelp +++ b/release-packaging/HelpSource/Classes/FluidNMFMatch.schelp @@ -4,9 +4,9 @@ CATEGORIES:: Libraries>FluidDecomposition RELATED:: Guides/FluCoMa, Guides/FluidDecomposition, Classes/FluidBufNMF DESCRIPTION:: -The FluidNMFMatch object matches an incoming audio signal against a set of spectral templates using an slimmed-down version of Nonnegative Matrix Factorisation (NMF)footnote:: Lee, Daniel D., and H. Sebastian Seung. 1999. ‘Learning the Parts of Objects by Non-Negative Matrix Factorization’. Nature 401 (6755): 788–91. https://doi.org/10.1038/44565. +The FluidNMFMatch object matches an incoming audio signal against a set of spectral templates using an slimmed-down version of Nonnegative Matrix Factorisation (NMF) footnote:: Lee, Daniel D., and H. Sebastian Seung. 1999. ‘Learning the Parts of Objects by Non-Negative Matrix Factorization’. Nature 401 (6755): 788–91. https://doi.org/10.1038/44565. :: -It outputs at kr the degree of detected match for each template (the activation amount, in NMF-terms). The spectral templates are presumed to have been produced by the offline NMF process (link::Classes/FluidBufNMF::), and must be the correct size with respect to the FFT settings being used (FFT size / 2 + 1 frames long). The rank of the decomposition is determined by the number of channels in the supplied buffer of templates, up to a maximum set by the ::maxrank:: parameter. +It outputs at kr the degree of detected match for each template (the activation amount, in NMF-terms). The spectral templates are presumed to have been produced by the offline NMF process (link::Classes/FluidBufNMF::), and must be the correct size with respect to the FFT settings being used (FFT size / 2 + 1 frames long). The rank of the decomposition is determined by the number of channels in the supplied buffer of templates, up to a maximum set by the STRONG::maxRank:: parameter. NMF has been a popular technique in signal processing research for things like source separation and transcription footnote:: Smaragdis and Brown, Non-Negative Matrix Factorization for Polyphonic Music Transcription.::, although its creative potential is so far relatively unexplored. It works iteratively, by trying to find a combination of amplitudes ('activations') that yield the original magnitude spectrogram of the audio input when added together. By and large, there is no unique answer to this question (i.e. there are different ways of accounting for an evolving spectrum in terms of some set of templates and envelopes). In its basic form, NMF is a form of unsupervised learning: it starts with some random data and then converges towards something that minimizes the distance between its generated data and the original:it tends to converge very quickly at first and then level out. Fewer iterations mean less processing, but also less predictable results. @@ -14,23 +14,21 @@ The whole process can be related to a channel vocoder where, instead of fixed ba More information on possible musicianly uses of NMF are availabe in LINK::Guides/FluCoMa:: overview file. -FluidBufNMF is part of the Fluid Decomposition Toolkit of the FluCoMa project. footnote:: -This was made possible thanks to the FluCoMa project ( http://www.flucoma.org/ ) funded by the European Research Council ( https://erc.europa.eu/ ) under the European Union’s Horizon 2020 research and innovation programme (grant agreement No 725899). -:: +FluidBufNMF is part of the Fluid Decomposition Toolkit of the FluCoMa project. footnote::This was made possible thanks to the FluCoMa project ( http://www.flucoma.org/ ) funded by the European Research Council ( https://erc.europa.eu/ ) under the European Union’s Horizon 2020 research and innovation programme (grant agreement No 725899). :: CLASSMETHODS:: METHOD:: kr -The real-time processing method. It takes an audio or control input, and will yield a control stream in the form of a multichannel array of size STRONG::maxrank::. If the dictionary buffer has fewer than maxrank channels, the remaining outputs will be zeroed. +The real-time processing method. It takes an audio or control input, and will yield a control stream in the form of a multichannel array of size STRONG::maxRank:: . If the dictionary buffer has fewer than maxRank channels, the remaining outputs will be zeroed. ARGUMENT:: in The signal input to the factorisation process. ARGUMENT:: dictBufNum - The server index of the buffer containing the different dictionaries that the input signal will be matched against. Dictionaries must be STRONG::(fft size / 2) + 1:: frames. If the buffer has more than STRONG::(maxrank) channels, the excess will be ignored. + The server index of the buffer containing the different dictionaries that the input signal will be matched against. Dictionaries must be STRONG::(fft size / 2) + 1:: frames. If the buffer has more than STRONG::maxRank:: channels, the excess will be ignored. -ARGUMENT::maxrank +ARGUMENT::maxRank The maximum number of elements the NMF algorithm will try to divide the spectrogram of the source in. This dictates the number of output channelsfor the ugen. ARGUMENT:: nIter @@ -45,12 +43,73 @@ ARGUMENT:: hopSize ARGUMENT:: fftSize The FFT/IFFT size. It should be at least 4 samples long, at least the size of the window, and a power of 2. Making it larger allows an oversampling of the spectral precision. Default = winSize -returns:: +RETURNS:: A multichannel kr output, giving for each dictionary component the activation amount. EXAMPLES:: -yes +STRONG::A pick compressor:: + CODE:: +//set some buffers +( +b = Buffer.read(s,File.realpath(FluidBufNMF.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-AaS-AcousticStrums-M.wav"); +c = Buffer.new(s); +x = Buffer.new(s); +e = Buffer.alloc(s,1,1); +) + +// train only 2 seconds +( +Routine { + FluidBufNMF.process(s,b.bufnum,0,88200,0,1, c.bufnum, x.bufnum, rank:10,fftSize:2048); + s.sync; + c.query; +}.play; +) + +// find the rank that has the picking sound by changing which channel to listen to +( + ~element = 8; + {PlayBuf.ar(10,c.bufnum)[~element]}.play +) + +// copy all the other ranks on itself and the picking dictionnary as the sole component of the 1st channel +( +Routine{ + (0..9).remove(~element).do({|chan|FluidBufCompose.process(s,srcBufNumA: x.bufnum, startChanA:chan, nChansA: 1, srcBufNumB: e.bufnum, dstBufNum: e.bufnum)}); + s.sync; + e.query; + s.sync; + FluidBufCompose.process(s,srcBufNumA: x.bufnum, startChanA: ~element, nChansA: 1, srcBufNumB: e.bufnum, dstStartChanB: 1, dstBufNum: e.bufnum); + s.sync; + e.query; +}.play; +) +e.plot +//using this trained dictionary we can see the envelop (activations) of each rank +{FluidNMFMatch.kr(PlayBuf.ar(1,b.bufnum),e.bufnum,2,fftSize:2048)}.plot(10); + +//we can then use the activation value to sidechain a compression patch that is sent in a delay +( +{ +var source, todelay, delay1, delay2, delay3; +source = PlayBuf.ar(1,b.bufnum); + todelay = DelayN.ar(source,0.1, 256/44100, Ramp.ar(K2A.ar(FluidNMFMatch.kr(source,e.bufnum,2)[0]),512/44100)); + + +}.play; +) + +:: +STRONG::Object finder:: + CODE:: + //indeed +:: + STRONG::Pretrained piano:: + CODE:: + //indeed +:: + STRONG::Strange Resonators:: CODE:: //indeed :: From 89ceda219b003936784c1cd7de58f9fb922a5794 Mon Sep 17 00:00:00 2001 From: Pierre Alexandre Tremblay Date: Mon, 5 Nov 2018 20:44:21 +0000 Subject: [PATCH 007/110] found a bug in the buffer summing of both nmf examples --- .../HelpSource/Classes/FluidBufNMF.schelp | 23 ++++++++++--------- .../HelpSource/Classes/FluidNMFMatch.schelp | 10 ++++---- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/release-packaging/HelpSource/Classes/FluidBufNMF.schelp b/release-packaging/HelpSource/Classes/FluidBufNMF.schelp index 07b7b9b..6da8b6e 100644 --- a/release-packaging/HelpSource/Classes/FluidBufNMF.schelp +++ b/release-packaging/HelpSource/Classes/FluidBufNMF.schelp @@ -187,7 +187,7 @@ c.plot;x.plot; y.plot; CODE:: - //set some buffers +//set some buffers ( b = Buffer.read(s,File.realpath(FluidBufNMF.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-AaS-AcousticStrums-M.wav"); c = Buffer.new(s); @@ -196,7 +196,7 @@ e = Buffer.alloc(s,1,1); y = Buffer.alloc(s,1,1); ) - // train only 2 seconds +// train only 2 seconds ( Routine { FluidBufNMF.process(s,b.bufnum,0,88200,0,1, c.bufnum, x.bufnum, rank:10); @@ -207,27 +207,28 @@ Routine { // find the rank that has the picking sound by changing which channel to listen to ( - ~element = 0; + ~element = 9; {PlayBuf.ar(10,c.bufnum)[~element]}.play ) // copy all the other ranks on itself and the picking dictionnary as the sole component of the 1st channel ( Routine{ - (0..9).remove(~element).do({|chan|FluidBufCompose.process(s,srcBufNumA: x.bufnum, startChanA:chan, nChansA: 1, srcBufNumB: e.bufnum, dstBufNum: e.bufnum)}); - s.sync; - e.query; - s.sync; - FluidBufCompose.process(s,srcBufNumA: x.bufnum, startChanA: ~element, nChansA: 1, srcBufNumB: e.bufnum, dstStartChanB: 1, dstBufNum: e.bufnum); - s.sync; - e.query; + z = (0..9); + FluidBufCompose.process(s,srcBufNumA: x.bufnum, startChanA: z.removeAt(~element), nChansA: 1, srcBufNumB: e.bufnum, dstBufNum: e.bufnum); + s.sync; + e.query; + s.sync; + z.do({|chan|FluidBufCompose.process(s,srcBufNumA: x.bufnum, startChanA:chan, nChansA: 1, dstStartChanA: 1, srcBufNumB: e.bufnum, dstBufNum: e.bufnum)}); + s.sync; + e.query; }.play; ) //process the whole file, splitting it with the 2 trained dictionnaries ( Routine{ - FluidBufNMF.process(s, b.bufnum, dstBufNum: c.bufnum, dictBufNum: e.bufnum, dictFlag: 2, actBufNum:y.bufnum, rank:2); + FluidBufNMF.process(s, b.bufnum, dstBufNum: c.bufnum, dictBufNum: e.bufnum, dictFlag: 2, actBufNum: y.bufnum, rank:2); s.sync; c.query; }.play; diff --git a/release-packaging/HelpSource/Classes/FluidNMFMatch.schelp b/release-packaging/HelpSource/Classes/FluidNMFMatch.schelp index 6e792ba..149de90 100644 --- a/release-packaging/HelpSource/Classes/FluidNMFMatch.schelp +++ b/release-packaging/HelpSource/Classes/FluidNMFMatch.schelp @@ -69,23 +69,25 @@ Routine { // find the rank that has the picking sound by changing which channel to listen to ( - ~element = 8; + ~element = 4; {PlayBuf.ar(10,c.bufnum)[~element]}.play ) // copy all the other ranks on itself and the picking dictionnary as the sole component of the 1st channel ( Routine{ - (0..9).remove(~element).do({|chan|FluidBufCompose.process(s,srcBufNumA: x.bufnum, startChanA:chan, nChansA: 1, srcBufNumB: e.bufnum, dstBufNum: e.bufnum)}); + z = (0..9); + FluidBufCompose.process(s,srcBufNumA: x.bufnum, startChanA: z.removeAt(~element), nChansA: 1, srcBufNumB: e.bufnum, dstBufNum: e.bufnum); s.sync; e.query; s.sync; - FluidBufCompose.process(s,srcBufNumA: x.bufnum, startChanA: ~element, nChansA: 1, srcBufNumB: e.bufnum, dstStartChanB: 1, dstBufNum: e.bufnum); + z.do({|chan|FluidBufCompose.process(s,srcBufNumA: x.bufnum, startChanA:chan, nChansA: 1, dstStartChanA: 1, srcBufNumB: e.bufnum, dstBufNum: e.bufnum)}); s.sync; e.query; }.play; ) -e.plot +e.plot; + //using this trained dictionary we can see the envelop (activations) of each rank {FluidNMFMatch.kr(PlayBuf.ar(1,b.bufnum),e.bufnum,2,fftSize:2048)}.plot(10); From 680b268804d046a778812eefb64ca7295f5aa38d Mon Sep 17 00:00:00 2001 From: Owen Green Date: Mon, 5 Nov 2018 22:56:28 +0000 Subject: [PATCH 008/110] src/FluidNMFMatch/FluidNMFMatch.cpp: Process all the samples --- src/FluidNMFMatch/FluidNMFMatch.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FluidNMFMatch/FluidNMFMatch.cpp b/src/FluidNMFMatch/FluidNMFMatch.cpp index 048015a..11c44e1 100644 --- a/src/FluidNMFMatch/FluidNMFMatch.cpp +++ b/src/FluidNMFMatch/FluidNMFMatch.cpp @@ -48,7 +48,7 @@ namespace nmf{ mMaxRank = parameter::lookupParam("maxrank", mClient->getParams()).getLong(); - mClient->set_host_buffer_size(bufferSize()); + mClient->set_host_buffer_size(mWorld->mFullRate.mBufLength); mClient->reset(); inputSignals[0] = SignalPointer(new AudioSignalWrapper()); From e05589e07dcf291333dafb8f2b8f926491e1907f Mon Sep 17 00:00:00 2001 From: Owen Green Date: Wed, 7 Nov 2018 17:31:51 +0000 Subject: [PATCH 009/110] src/FluidNMFMatch/FluidNMFMatch.cpp: Fix hilarious off-by-one error reeading input pointer, caused by not knowing what zin() does --- src/FluidNMFMatch/FluidNMFMatch.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/FluidNMFMatch/FluidNMFMatch.cpp b/src/FluidNMFMatch/FluidNMFMatch.cpp index 11c44e1..b7947e8 100644 --- a/src/FluidNMFMatch/FluidNMFMatch.cpp +++ b/src/FluidNMFMatch/FluidNMFMatch.cpp @@ -104,14 +104,14 @@ namespace nmf{ } } - void next(int numsamples) + void next(int) { auto filters = parameter::lookupParam("filterbuf", mClient->getParams()).getBuffer(); if(!filters) return; setParams(false); - const float* input = zin(0); + const float* input = in(0); const float inscalar = in0(0); inputSignals[0]->set(const_cast(input), inscalar); @@ -126,7 +126,7 @@ namespace nmf{ out0(i) = outputSignals[i]->next(); for(size_t i = actualRank; i < mMaxRank; ++i) - out0(i) = outputSignals[i]->next(); + out0(i) = 0; // outputSignals[i]->next(); } From 03306e76953ff7c7fe35900fe8cf365a658a597b Mon Sep 17 00:00:00 2001 From: Owen Green Date: Wed, 14 Nov 2018 21:14:18 +0000 Subject: [PATCH 010/110] src/FluidNMFMatch/FluidNMFMatch.cpp: Fix rank overrun on output --- src/FluidNMFMatch/FluidNMFMatch.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FluidNMFMatch/FluidNMFMatch.cpp b/src/FluidNMFMatch/FluidNMFMatch.cpp index b7947e8..10d4e7d 100644 --- a/src/FluidNMFMatch/FluidNMFMatch.cpp +++ b/src/FluidNMFMatch/FluidNMFMatch.cpp @@ -121,7 +121,7 @@ namespace nmf{ mClient->do_process_noOLA(inputSignals.begin(),inputSignals.end(), outputSignals.begin(), outputSignals.end(), mWorld->mFullRate.mBufLength ,1,mMaxRank); parameter::BufferAdaptor::Access buf(filters); - long actualRank = buf.numChans(); + long actualRank = std::min(buf.numChans(),mMaxRank); for(size_t i = 0; i < actualRank; ++i) out0(i) = outputSignals[i]->next(); From e4405134c44b975e0b65b79f1e92be402baa7102 Mon Sep 17 00:00:00 2001 From: Owen Green Date: Fri, 16 Nov 2018 11:50:22 +0000 Subject: [PATCH 011/110] include/fdNRTBase.hpp: Add BufferAdaptor::exists() --- include/fdNRTBase.hpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/include/fdNRTBase.hpp b/include/fdNRTBase.hpp index 3e8e0e1..2004fd3 100644 --- a/include/fdNRTBase.hpp +++ b/include/fdNRTBase.hpp @@ -94,6 +94,10 @@ namespace sc{ return (mBuffer && mBufnum >=0 && mBufnum < mWorld->mNumSndBufs); } + bool exists() const override { + return mBufnum >=0 && mBufnum < mWorld->mNumSndBufs; + } + FluidTensorView samps(size_t channel, size_t rankIdx = 0) override { FluidTensorView v{mBuffer->data,0, static_cast(mBuffer->frames),static_cast(mBuffer->channels)}; @@ -169,6 +173,10 @@ namespace sc{ return (mBuffer && mBufnum >=0 && mBufnum < mWorld->mNumSndBufs); } + bool exists() const override { + return mBufnum >=0 && mBufnum < mWorld->mNumSndBufs; + } + FluidTensorView samps(size_t channel, size_t rankIdx = 0) override { FluidTensorView v{mBuffer->data,0, static_cast(mBuffer->frames),static_cast(mBuffer->channels)}; From a3dab0b844af975a9867935ed607dcaeadef5192 Mon Sep 17 00:00:00 2001 From: Owen Green Date: Fri, 16 Nov 2018 11:50:53 +0000 Subject: [PATCH 012/110] src/FluidNMFMatch/FluidNMFMatch.cpp: Only check for buffer after it might have changed --- src/FluidNMFMatch/FluidNMFMatch.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/FluidNMFMatch/FluidNMFMatch.cpp b/src/FluidNMFMatch/FluidNMFMatch.cpp index 10d4e7d..0e3f610 100644 --- a/src/FluidNMFMatch/FluidNMFMatch.cpp +++ b/src/FluidNMFMatch/FluidNMFMatch.cpp @@ -106,11 +106,13 @@ namespace nmf{ void next(int) { + + setParams(false); auto filters = parameter::lookupParam("filterbuf", mClient->getParams()).getBuffer(); if(!filters) return; - setParams(false); + const float* input = in(0); const float inscalar = in0(0); inputSignals[0]->set(const_cast(input), inscalar); From 3e5a82fb543167f4ecb348239bef7b34e99d180f Mon Sep 17 00:00:00 2001 From: Pierre Alexandre Tremblay Date: Sat, 17 Nov 2018 12:39:41 +0000 Subject: [PATCH 013/110] fixed default bufnmf and nmfmatch, and ported all nmfmatch examples but one --- icon.png | Bin 0 -> 43228 bytes release-packaging/Classes/FluidBufNMF.sc | 2 +- release-packaging/Classes/FluidNMFMatch.sc | 2 +- .../HelpSource/Classes/FluidBufNMF.schelp | 52 +++++ .../HelpSource/Classes/FluidNMFMatch.schelp | 215 +++++++++++++++++- src/FluidNMFMatch/test2.scd | 175 ++++++++++++++ 6 files changed, 434 insertions(+), 12 deletions(-) create mode 100644 icon.png create mode 100644 src/FluidNMFMatch/test2.scd diff --git a/icon.png b/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..448f29f58c4a46feeb039d001354b0a0bb3a614d GIT binary patch literal 43228 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 0.01, {i + 21})}).reject({arg item; item.isNil}).postln; + }, '/activations'); +) + :: STRONG::Strange Resonators:: CODE:: - //indeed + //to be completed :: \ No newline at end of file diff --git a/src/FluidNMFMatch/test2.scd b/src/FluidNMFMatch/test2.scd new file mode 100644 index 0000000..6c61f88 --- /dev/null +++ b/src/FluidNMFMatch/test2.scd @@ -0,0 +1,175 @@ +//designing the guitar example +( +{ + var source, todelay, delay1, delay2, delay3, feedback, mod1, mod2, mod3, mod4; + //read the source + source = PlayBuf.ar(1, b.bufnum); + + // generate modulators that are coprime in frequency + mod1 = SinOsc.ar(1, 0, 0.001); + mod2 = SinOsc.ar(((617 * 181) / (461 * 991)), 0, 0.001); + mod3 = SinOsc.ar(((607 * 193) / (491 * 701)), 0, 0.001); + mod4 = SinOsc.ar(((613 * 191) / (463 * 601)), 0, 0.001); + + // compress the signal to send to the delays + todelay = DelayN.ar(source,0.1, 800/44100, //delaying it to compensate for FluidNMFMatch's latency + LagUD.ar(K2A.ar(FluidNMFMatch.kr(source,e.bufnum,2,fftSize:2048)[0]), //reading the channel of the activations on the pick dictionary + 80/44100, // lag uptime (compressor's attack) + 1000/44100, // lag downtime (compressor's decay) + (1/(2.dbamp) // compressor's threshold inverted + )).clip(1,1000).pow((8.reciprocal)-1)); //clipping it so we only affect above threshold, then ratio(8) becomes the exponent of that base + + // delay network + feedback = LocalIn.ar(3);// take the feedback in for the delays + delay1 = DelayC.ar(BPF.ar(todelay+feedback[1]+(feedback[2] * 0.3), 987, 6.7,0.8),0.123,0.122+(mod1*mod2)); + delay2 = DelayC.ar(BPF.ar(todelay+feedback[0]+(feedback[2] * 0.3), 1987, 6.7,0.8),0.345,0.344+(mod3*mod4)); + delay3 = DelayC.ar(BPF.ar(todelay+feedback[1], 1456, 6.7,0.8),0.567,0.566+(mod1*mod3),0.6); + LocalOut.ar([delay1,delay2, delay3]); // write the feedback for the delays + + //listen to the delays only by uncommenting the following line + // [delay1+delay3,delay2+delay3] + source.dup + ([delay1+delay3,delay2+delay3]*(-3.dbamp)) +}.play; +) + + +//doing the piano training +//load in the sound in and a pretrained dictionary +( +b = Buffer.read(s,File.realpath(FluidNMFMatch.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-SA-UprightPianoPedalWide.wav"); +c = Buffer.read(s,File.realpath(FluidNMFMatch.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/filters/piano-dicts.wav"); +) +b.play +c.query + +//bufcompose a 88 note version +// d = Buffer.alloc(s,1); +// FluidBufCompose.process(s,c.bufnum,nChansA:88,srcBufNumB:d.bufnum,dstBufNum:d.bufnum) +// d.query +// d.write("/Users/pa/Documents/documents@hudd/research/projects/fluid corpus navigation/research/fluid_decomposition/AudioFiles/filters/piano-dicts.wav", "wav", "float32") + +//now use the activations of each notes to generate a resynth +( +x = { arg dabuf = 1; + var source, resynth; + source = PlayBuf.ar(2, b.bufnum,loop:1).sum; + resynth = SinOsc.ar((21..108).midicps, 0, FluidNMFMatch.kr(source,dabuf,88,10,4096).madd(0.002)).sum; + [source, resynth] +}.play +) + +d = Buffer.read(s,"/Users/pa/Desktop/piano-dicts/seed-dc-01.wav"); + +x.set(\dabuf, d.bufnum) + +//now sample and hold the same stream to get notes identified, played and sent back via osc +( +{ + var source, resynth, chain, trig, acts; + source = PlayBuf.ar(2,b.bufnum,loop:1).sum; + + // built in attack detection, delayed until the stable part of the sound + chain = FFT(LocalBuf(256), source); + trig = TDelay.kr(Onsets.kr(chain, 0.5),0.1); + + // samples and holds activation values that are scaled and capped, in effect thresholding them + acts = Latch.kr(FluidNMFMatch.kr(source,c.bufnum,88,10,4096).linlin(15,20,0,0.1),trig); + + // resynths as in the previous example, with the values sent back to the language + resynth = SinOsc.ar((21..108).midicps, 0, acts).sum; + SendReply.kr(trig, '/activations', acts); + [source, resynth] + // [source, T2A.ar(trig)] + // resynth +}.play +) + +// define a receiver for the activations +( +OSCdef(\listener, {|msg| + var data = msg[3..]; + // removes the silent and spits out the indicies as midinote number + data.collect({arg item, i; if (item > 0.01, {i + 21})}).reject({arg item; item.isNil}).postln; +}, '/activations'); +) + + + +// (0..10).collect({arg item, i; if (item > 5, {i})}).reject({arg item; item.isNil}).postln; +///////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +//set some buffers +( +b = Buffer.read(s,File.realpath(FluidNMFMatch.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-BaB-SoundscapeGolcarWithDog.wav"); +c = Buffer.new(s); +x = Buffer.new(s); +e = Buffer.alloc(s,1,1); +) + +// train where all objects are present +( +Routine { + FluidBufNMF.process(s,b.bufnum,130000,150000,0,1, c.bufnum, x.bufnum, rank:10); + s.sync; + c.query; +}.play; +) + +// wait for the query to print +// then find a rank for each item you want to find. You could also sum them. Try to find a rank with a good object-to-rest ratio +( + ~dog =0; + {PlayBuf.ar(10,c.bufnum)[~dog]}.play +) + +( + ~bird = 5; + {PlayBuf.ar(10,c.bufnum)[~bird]}.play +) + + +// copy at least one other rank to a third rank, a sort of left-over channel +( +Routine{ + FluidBufCompose.process(s,srcBufNumA: x.bufnum, startChanA:~dog, nChansA: 1, srcBufNumB: e.bufnum, dstBufNum: e.bufnum); + FluidBufCompose.process(s,srcBufNumA: x.bufnum, startChanA:~bird, nChansA: 1, dstStartChanA: 1, srcBufNumB: e.bufnum, dstBufNum: e.bufnum); + s.sync; + (0..9).removeAll([~dog,~bird]).do({|chan|FluidBufCompose.process(s,srcBufNumA: x.bufnum, startChanA:chan, nChansA: 1, dstStartChanA: 2, srcBufNumB: e.bufnum, dstBufNum: e.bufnum)}); + s.sync; + e.query; +}.play; +) +e.plot; + +//using this trained dictionary we can then see the activation... +( +{ + var source, blips; + //read the source + source = PlayBuf.ar(2, b.bufnum); + blips = FluidNMFMatch.kr(source.sum,e.bufnum,3); + }.plot(10); +) + +// ...and use some threshold to 'find' objects... +( +{ + var source, blips; + //read the source + source = PlayBuf.ar(2, b.bufnum); + blips = Schmidt.kr(FluidNMFMatch.kr(source.sum,e.bufnum,3),0.5,[10,1,1000]); + }.plot(10); +) + +// ...and use these to sonify them +( +{ + var source, blips, dogs, birds; + //read the source + source = PlayBuf.ar(2, b.bufnum); + blips = Schmidt.kr(FluidNMFMatch.kr(source.sum,e.bufnum,3),0.5,[10,1,1000]); + dogs = SinOsc.ar(100,0,Lag.kr(blips[0],0.05,0.15)); + birds = SinOsc.ar(1000,0,Lag.kr(blips[1],0.05,0.05)); + [dogs, birds] + source; + }.play; +) From ed5e8dafa18ba45a0219c4484036feb8c4a72a45 Mon Sep 17 00:00:00 2001 From: Pierre Alexandre Tremblay Date: Mon, 28 Jan 2019 18:25:54 +0000 Subject: [PATCH 014/110] *TransientSlice : both declarations and Helpfiles updated for bug fix and new parameter --- .../Classes/FluidBufExperiments.sc | 29 ------------------- .../Classes/FluidBufTransientSlice.sc | 4 +-- .../Classes/FluidTransientSlice.sc | 4 +-- .../Classes/FluidBufTransientSlice.schelp | 9 ++++-- .../Classes/FluidTransientSlice.schelp | 9 ++++-- 5 files changed, 16 insertions(+), 39 deletions(-) delete mode 100644 release-packaging/Classes/FluidBufExperiments.sc diff --git a/release-packaging/Classes/FluidBufExperiments.sc b/release-packaging/Classes/FluidBufExperiments.sc deleted file mode 100644 index aed0c97..0000000 --- a/release-packaging/Classes/FluidBufExperiments.sc +++ /dev/null @@ -1,29 +0,0 @@ -FluidBufExperiments { - - *allocMatch{|server, srcbuf, rank=1| - var dstbuf,srcbufnum; - - srcbufnum = srcbuf.bufnum; - - server = server ? Server.default; - dstbuf = Buffer.new(server:server,numFrames:0,numChannels:1); - - server.listSendMsg( - [\b_gen, srcbufnum, "BufMatch",dstbuf.bufnum, rank] - ); - ^dstbuf; - } - - - *allocMatchAsync{|server, srcbuf, rank=1| - - var dstbuf,srcbufnum; - - srcbufnum = srcbuf.bufnum; - server = server ? Server.default; - dstbuf = Buffer.new(server:server,numFrames:0,numChannels:1); - server.sendMsg(\cmd, \AsyncBufMatch, srcbufnum, dstbuf.bufnum, rank); - ^dstbuf; - } - -} diff --git a/release-packaging/Classes/FluidBufTransientSlice.sc b/release-packaging/Classes/FluidBufTransientSlice.sc index bdda702..fd8c4bb 100644 --- a/release-packaging/Classes/FluidBufTransientSlice.sc +++ b/release-packaging/Classes/FluidBufTransientSlice.sc @@ -1,11 +1,11 @@ FluidBufTransientSlice{ - *process { arg server, srcBufNum, startAt = 0, nFrames = -1, startChan = 0, nChans = -1, transBufNum, order = 200, blockSize = 2048, padSize = 1024, skew = 0, threshFwd = 3, threshBack = 1.1, winSize = 14, debounce = 25; + *process { arg server, srcBufNum, startAt = 0, nFrames = -1, startChan = 0, nChans = -1, transBufNum, order = 200, blockSize = 2048, padSize = 1024, skew = 0, threshFwd = 3, threshBack = 1.1, winSize = 14, debounce = 25, minSlice = 1000; if(srcBufNum.isNil) { Error("Invalid buffer").format(thisMethod.name, this.class.name).throw}; if(transBufNum.isNil) { Error("Invalid buffer").format(thisMethod.name, this.class.name).throw}; server = server ? Server.default; - server.sendMsg(\cmd, \BufTransientSlice, srcBufNum, startAt, nFrames, startChan, nChans, transBufNum, order, blockSize, padSize, skew, threshFwd, threshBack, winSize, debounce); + server.sendMsg(\cmd, \BufTransientSlice, srcBufNum, startAt, nFrames, startChan, nChans, transBufNum, order, blockSize, padSize, skew, threshFwd, threshBack, winSize, debounce, minSlice); } } diff --git a/release-packaging/Classes/FluidTransientSlice.sc b/release-packaging/Classes/FluidTransientSlice.sc index 5ef269a..00ae369 100644 --- a/release-packaging/Classes/FluidTransientSlice.sc +++ b/release-packaging/Classes/FluidTransientSlice.sc @@ -1,5 +1,5 @@ FluidTransientSlice : UGen { - *ar { arg in = 0, order = 20, blockSize = 256, padSize = 128, skew = 0.0, threshFwd = 3.0, threshBack = 1.1, winSize=14, debounce=25; - ^this.multiNew('audio', in.asAudioRateInput(this), order, blockSize, padSize, skew, threshFwd ,threshBack, winSize, debounce) + *ar { arg in = 0, order = 20, blockSize = 256, padSize = 128, skew = 0.0, threshFwd = 3.0, threshBack = 1.1, winSize=14, debounce=25, minSlice = 1000; + ^this.multiNew('audio', in.asAudioRateInput(this), order, blockSize, padSize, skew, threshFwd ,threshBack, winSize, debounce, minSlice) } } \ No newline at end of file diff --git a/release-packaging/HelpSource/Classes/FluidBufTransientSlice.schelp b/release-packaging/HelpSource/Classes/FluidBufTransientSlice.schelp index d693f36..16af951 100644 --- a/release-packaging/HelpSource/Classes/FluidBufTransientSlice.schelp +++ b/release-packaging/HelpSource/Classes/FluidBufTransientSlice.schelp @@ -58,7 +58,10 @@ ARGUMENT:: winSize The averaging window of the error detection function. It needs smoothing as it is very jittery. The longer the window, the less precise, but the less false positives. ARGUMENT:: debounce - The window size in sample within which positive detections will be clumped together to avoid overdetecting in time. No slice will be shorter than this duration. + The window size in sample within which positive detections will be clumped together to avoid overdetecting in time. + +ARGUMENT:: minSlice + The minimum duration of a slice in samples. RETURNS:: Nothing, as the destination buffer is declared in the function call. @@ -77,7 +80,7 @@ c = Buffer.new(s); ( Routine{ t = Main.elapsedTime; - FluidBufTransientSlice.process(s,b.bufnum, transBufNum:c.bufnum, order:80, debounce:4410); + FluidBufTransientSlice.process(s,b.bufnum, transBufNum:c.bufnum, order:80, minSlice:4410); s.sync; (Main.elapsedTime - t).postln; }.play @@ -105,7 +108,7 @@ c.query; ( Routine{ t = Main.elapsedTime; - FluidBufTransients.process(s,b.bufnum, 44100, 44100, 0, 0, c.bufnum, d.bufnum, 100, 512,256,1,2,1,12,20); + FluidBufTransients.process(s,b.bufnum, 44100, 44100, 0, 0, c.bufnum, d.bufnum, 100, 512,256,1,2,1,12,20,441); s.sync; (Main.elapsedTime - t).postln; }.play diff --git a/release-packaging/HelpSource/Classes/FluidTransientSlice.schelp b/release-packaging/HelpSource/Classes/FluidTransientSlice.schelp index fd1ea88..2b9ea03 100644 --- a/release-packaging/HelpSource/Classes/FluidTransientSlice.schelp +++ b/release-packaging/HelpSource/Classes/FluidTransientSlice.schelp @@ -38,7 +38,10 @@ ARGUMENT:: winSize The averaging window of the error detection function. It needs smoothing as it is very jittery. The longer the window, the less precise, but the less false positives. ARGUMENT:: debounce - The window size in sample within with positive detections will be clumped together to avoid overdetecting in time. No slice will be shorter than this duration. + The window size in sample within with positive detections will be clumped together to avoid overdetecting in time. + +ARGUMENT:: minSlice + The minimum duration of a slice in samples. RETURNS:: An audio stream with impulses at detected transients. The latency between the input and the output is (blockSize + padSize - order) samples. @@ -54,14 +57,14 @@ b = Buffer.read(s,File.realpath(FluidTransientSlice.class.filenameSymbol).dirnam {var sig = PlayBuf.ar(1,b.bufnum,loop:1); [FluidTransientSlice.ar(sig)*0.5, DelayN.ar(sig, 1, ((256 + 128 - 20)/ s.sampleRate))]}.play // other parameters -{var sig = PlayBuf.ar(1,b.bufnum,loop:1); [FluidTransientSlice.ar(sig,order:80,debounce:2205)*0.5, DelayN.ar(sig, 1, ((256 + 128 - 80)/ s.sampleRate))]}.play +{var sig = PlayBuf.ar(1,b.bufnum,loop:1); [FluidTransientSlice.ar(sig,order:80,minSlice:2205)*0.5, DelayN.ar(sig, 1, ((256 + 128 - 80)/ s.sampleRate))]}.play // more musical trans-trigged autopan ( { var sig, trig, syncd, pan; sig = PlayBuf.ar(1,b.bufnum,loop:1); - trig = FluidTransientSlice.ar(sig,order:10,debounce:2205); + trig = FluidTransientSlice.ar(sig,order:10,minSlice:4410); syncd = DelayN.ar(sig, 1, ((256 + 128 - 10)/ s.sampleRate)); pan = TRand.ar(-1,1,trig); Pan2.ar(syncd,pan); From b897857daec1a544699e0b4bd5219b15826d5935 Mon Sep 17 00:00:00 2001 From: Pierre Alexandre Tremblay Date: Thu, 31 Jan 2019 11:46:19 +0000 Subject: [PATCH 015/110] added example folder in the package, plus typos --- .../HelpSource/Classes/FluidBufCompose.schelp | 2 +- .../HelpSource/Classes/FluidBufHPSS.schelp | 101 +++++++++--------- .../Classes/FluidBufTransients.schelp | 5 +- .../HelpSource/Classes/FluidGain.schelp | 22 ++-- .../HelpSource/Classes/FluidHPSS.schelp | 31 +++--- .../HelpSource/Classes/FluidSTFTPass.schelp | 32 +++--- .../Classes/FluidTransientSlice.schelp | 1 - .../buffer_compositing/fileiterator.sc | 37 +++++++ .../ignore/Examples/nmf/JiT-NMF.scd | 57 ++++++++++ 9 files changed, 188 insertions(+), 100 deletions(-) create mode 100644 release-packaging/ignore/Examples/buffer_compositing/fileiterator.sc create mode 100644 release-packaging/ignore/Examples/nmf/JiT-NMF.scd diff --git a/release-packaging/HelpSource/Classes/FluidBufCompose.schelp b/release-packaging/HelpSource/Classes/FluidBufCompose.schelp index 43e9b31..01c6493 100644 --- a/release-packaging/HelpSource/Classes/FluidBufCompose.schelp +++ b/release-packaging/HelpSource/Classes/FluidBufCompose.schelp @@ -183,4 +183,4 @@ f.play; // compare with the original b.play; - :: \ No newline at end of file +:: diff --git a/release-packaging/HelpSource/Classes/FluidBufHPSS.schelp b/release-packaging/HelpSource/Classes/FluidBufHPSS.schelp index dae40e5..57e9398 100644 --- a/release-packaging/HelpSource/Classes/FluidBufHPSS.schelp +++ b/release-packaging/HelpSource/Classes/FluidBufHPSS.schelp @@ -115,55 +115,54 @@ Discussion:: EXAMPLES:: code:: - //load buffers - ( - b = Buffer.read(s,File.realpath(FluidBufHPSS.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-AaS-SynthTwoVoices-M.wav"); - c = Buffer.new(s); - d = Buffer.new(s); - e = Buffer.new(s); - ) - - // run with basic parameters - ( - Routine{ - t = Main.elapsedTime; - FluidBufHPSS.process(s, b.bufnum, harmBufNum: c.bufnum, percBufNum: d.bufnum); +//load buffers +( + b = Buffer.read(s,File.realpath(FluidBufHPSS.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-AaS-SynthTwoVoices-M.wav"); + c = Buffer.new(s); + d = Buffer.new(s); + e = Buffer.new(s); +) + +// run with basic parameters +( + Routine{ + t = Main.elapsedTime; + FluidBufHPSS.process(s, b.bufnum, harmBufNum: c.bufnum, percBufNum: d.bufnum); + s.sync; + (Main.elapsedTime - t).postln; + }.play +) + +//query and play the harmonic +c.query; +c.play; +//querry and play the percussive +d.query; +d.play; + +//nullsumming tests +{(PlayBuf.ar(1,c.bufnum))+(PlayBuf.ar(1,d.bufnum))+(-1*PlayBuf.ar(1,b.bufnum,doneAction:2))}.play + +//more daring parameters, in mode 2 +( + Routine{ + t = Main.elapsedTime; + FluidBufHPSS.process(s, b.bufnum, harmBufNum: c.bufnum, percBufNum: d.bufnum, resBufNum:e.bufnum, harmFiltSize:31, modeFlag:2, htf1: 0.005, hta1: 7.5, htf2: 0.168, hta2: 7.5, ptf1: 0.004, pta1: 26.5, ptf2: 0.152, pta2: 26.5); s.sync; - (Main.elapsedTime - t).postln; - }.play - ) - - //query and play the harmonic - c.query; - c.play; - //querry and play the percussive - d.query; - d.play; - - //nullsumming tests - {(PlayBuf.ar(1,c.bufnum))+(PlayBuf.ar(1,d.bufnum))+(-1*PlayBuf.ar(1,b.bufnum,doneAction:2))}.play - - //more daring parameters, in mode 2 - ( - Routine{ - t = Main.elapsedTime; - FluidBufHPSS.process(s, b.bufnum, harmBufNum: c.bufnum, percBufNum: d.bufnum, resBufNum:e.bufnum, harmFiltSize:31, modeFlag:2, htf1: 0.005, hta1: 7.5, htf2: 0.168, hta2: 7.5, ptf1: 0.004, pta1: 26.5, ptf2: 0.152, pta2: 26.5); - s.sync; - (Main.elapsedTime - t).postln; - }.play - ) - - //query and play the harmonic - c.query; - c.play; - //query and play the percussive - d.query; - d.play; - //query and play the residual - e.query; - e.play; - - //still nullsumming - {PlayBuf.ar(1,c.bufnum) + PlayBuf.ar(1,d.bufnum) + PlayBuf.ar(1,e.bufnum) - PlayBuf.ar(1,b.bufnum,doneAction:2)}.play; - :: - \ No newline at end of file + (Main.elapsedTime - t).postln; + }.play +) + +//query and play the harmonic +c.query; +c.play; +//query and play the percussive +d.query; +d.play; +//query and play the residual +e.query; +e.play; + +//still nullsumming +{PlayBuf.ar(1,c.bufnum) + PlayBuf.ar(1,d.bufnum) + PlayBuf.ar(1,e.bufnum) - PlayBuf.ar(1,b.bufnum,doneAction:2)}.play; +:: diff --git a/release-packaging/HelpSource/Classes/FluidBufTransients.schelp b/release-packaging/HelpSource/Classes/FluidBufTransients.schelp index 812e9a6..1e5b3dd 100644 --- a/release-packaging/HelpSource/Classes/FluidBufTransients.schelp +++ b/release-packaging/HelpSource/Classes/FluidBufTransients.schelp @@ -109,7 +109,4 @@ Routine{ (Main.elapsedTime - t).postln; }.play ) - :: - - - \ No newline at end of file +:: diff --git a/release-packaging/HelpSource/Classes/FluidGain.schelp b/release-packaging/HelpSource/Classes/FluidGain.schelp index eb54159..b039a81 100644 --- a/release-packaging/HelpSource/Classes/FluidGain.schelp +++ b/release-packaging/HelpSource/Classes/FluidGain.schelp @@ -28,14 +28,14 @@ RETURNS:: EXAMPLES:: Summing with the inverse (gain of -1) with a delay of the latency gives us CPU-expensive silence. - CODE:: - { var source = PinkNoise.ar(0.1); DelayN.ar(source,delaytime:1000/s.sampleRate) + FluidGain.ar(source,1000,-1); }.play - :: - Varying the gain at audio rate. - CODE:: - { FluidGain.ar(PinkNoise.ar(0.1), gain:LFTri.ar(1)) }.play - :: - Varying the gain at comtrol rate, in beautiful stereo. - CODE:: - { FluidGain.ar(SinOsc.ar([222,333],mul:0.1), gain:LFTri.kr([0.5,0.7])) }.play - :: +CODE:: +{ var source = PinkNoise.ar(0.1); DelayN.ar(source,delaytime:1000/s.sampleRate) + FluidGain.ar(source,1000,-1); }.play +:: +Varying the gain at audio rate. +CODE:: +{ FluidGain.ar(PinkNoise.ar(0.1), gain:LFTri.ar(1)) }.play +:: +Varying the gain at comtrol rate, in beautiful stereo. +CODE:: +{ FluidGain.ar(SinOsc.ar([222,333],mul:0.1), gain:LFTri.kr([0.5,0.7])) }.play +:: diff --git a/release-packaging/HelpSource/Classes/FluidHPSS.schelp b/release-packaging/HelpSource/Classes/FluidHPSS.schelp index deff4aa..418cbd4 100644 --- a/release-packaging/HelpSource/Classes/FluidHPSS.schelp +++ b/release-packaging/HelpSource/Classes/FluidHPSS.schelp @@ -89,24 +89,23 @@ Discussion:: EXAMPLES:: CODE:: - //load a soundfile to play - b = Buffer.read(s,File.realpath(FluidHPSS.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-AaS-SynthTwoVoices-M.wav"); +//load a soundfile to play +b = Buffer.read(s,File.realpath(FluidHPSS.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-AaS-SynthTwoVoices-M.wav"); - // run with basic parameters (left is harmonic, right is percussive) - {FluidHPSS.ar(PlayBuf.ar(1,b.bufnum,loop:1))}.play +// run with basic parameters (left is harmonic, right is percussive) +{FluidHPSS.ar(PlayBuf.ar(1,b.bufnum,loop:1))}.play - // run in mode 1 - {FluidHPSS.ar(PlayBuf.ar(1,b.bufnum,loop:1),17,31,1,0.05,40,0.1,-40)}.play +// run in mode 1 +{FluidHPSS.ar(PlayBuf.ar(1,b.bufnum,loop:1),17,31,1,0.05,40,0.1,-40)}.play - // run in mode 2m listening to - //the harmonic stream - {FluidHPSS.ar(PlayBuf.ar(1,b.bufnum,loop:1),17,31,2,0.05,40,0.1,-40, 0.1, -10, 0.2, 10)[0].dup}.play - // the percussive stream - {FluidHPSS.ar(PlayBuf.ar(1,b.bufnum,loop:1),17,31,2,0.05,40,0.1,-40, 0.1, -10, 0.2, 10)[1].dup}.play - // the residual stream - {FluidHPSS.ar(PlayBuf.ar(1,b.bufnum,loop:1),17,31,2,0.05,40,0.1,-40, 0.1, -10, 0.2, 10)[2].dup}.play +// run in mode 2m listening to +//the harmonic stream +{FluidHPSS.ar(PlayBuf.ar(1,b.bufnum,loop:1),17,31,2,0.05,40,0.1,-40, 0.1, -10, 0.2, 10)[0].dup}.play +// the percussive stream +{FluidHPSS.ar(PlayBuf.ar(1,b.bufnum,loop:1),17,31,2,0.05,40,0.1,-40, 0.1, -10, 0.2, 10)[1].dup}.play +// the residual stream +{FluidHPSS.ar(PlayBuf.ar(1,b.bufnum,loop:1),17,31,2,0.05,40,0.1,-40, 0.1, -10, 0.2, 10)[2].dup}.play - // null test (the process add a latency of ((harmFiltSize + (winSize / hopSize) - 1) * hopSize) samples - {var sig = PlayBuf.ar(1,b.bufnum,loop:1); [FluidHPSS.ar(sig,17,31, winSize:1024,hopSize:512,fftSize:2048).sum - DelayN.ar(sig, 1, ((31 + 1) * 512 / s.sampleRate))]}.play +// null test (the process add a latency of ((harmFiltSize + (winSize / hopSize) - 1) * hopSize) samples +{var sig = PlayBuf.ar(1,b.bufnum,loop:1); [FluidHPSS.ar(sig,17,31, winSize:1024,hopSize:512,fftSize:2048).sum - DelayN.ar(sig, 1, ((31 + 1) * 512 / s.sampleRate))]}.play :: - \ No newline at end of file diff --git a/release-packaging/HelpSource/Classes/FluidSTFTPass.schelp b/release-packaging/HelpSource/Classes/FluidSTFTPass.schelp index 67c6806..1bc8260 100644 --- a/release-packaging/HelpSource/Classes/FluidSTFTPass.schelp +++ b/release-packaging/HelpSource/Classes/FluidSTFTPass.schelp @@ -31,19 +31,19 @@ RETURNS:: EXAMPLES:: - Summing with the inverse (gain of -1) with a delay of the latency gives us CPU-expensive silence. - CODE:: - { var source = PinkNoise.ar(0.1); DelayN.ar(source, delaytime:1024/s.sampleRate, mul: -1) + FluidSTFTPass.ar(source, 1024, 256, 1024); }.play - :: - Larger, oversampled, FFT - CODE:: - { FluidSTFTPass.ar(PinkNoise.ar(0.1), 2048, 128, 8192) }.play - :: - Stereo Input Tests. - CODE:: - { FluidSTFTPass.ar([SinOsc.ar(222,mul: 0.1), PinkNoise.ar(Decay.ar(Impulse.ar(0.666,mul: 0.2), 0.5))], fftSize:1024)}.play - :: - Stereo Parameter Tests. - CODE:: - { FluidSTFTPass.ar(SinOsc.ar(222,mul: 0.1), [1024,8192],256,8192)}.play - :: +Summing with the inverse (gain of -1) with a delay of the latency gives us CPU-expensive silence. +CODE:: +{ var source = PinkNoise.ar(0.1); DelayN.ar(source, delaytime:1024/s.sampleRate, mul: -1) + FluidSTFTPass.ar(source, 1024, 256, 1024); }.play +:: +Larger, oversampled, FFT +CODE:: +{ FluidSTFTPass.ar(PinkNoise.ar(0.1), 2048, 128, 8192) }.play +:: +Stereo Input Tests. +CODE:: +{ FluidSTFTPass.ar([SinOsc.ar(222,mul: 0.1), PinkNoise.ar(Decay.ar(Impulse.ar(0.666,mul: 0.2), 0.5))], fftSize:1024)}.play +:: +Stereo Parameter Tests. +CODE:: +{ FluidSTFTPass.ar(SinOsc.ar(222,mul: 0.1), [1024,8192],256,8192)}.play +:: diff --git a/release-packaging/HelpSource/Classes/FluidTransientSlice.schelp b/release-packaging/HelpSource/Classes/FluidTransientSlice.schelp index 2b9ea03..326f75b 100644 --- a/release-packaging/HelpSource/Classes/FluidTransientSlice.schelp +++ b/release-packaging/HelpSource/Classes/FluidTransientSlice.schelp @@ -71,4 +71,3 @@ b = Buffer.read(s,File.realpath(FluidTransientSlice.class.filenameSymbol).dirnam }.play ) :: - diff --git a/release-packaging/ignore/Examples/buffer_compositing/fileiterator.sc b/release-packaging/ignore/Examples/buffer_compositing/fileiterator.sc new file mode 100644 index 0000000..89cf368 --- /dev/null +++ b/release-packaging/ignore/Examples/buffer_compositing/fileiterator.sc @@ -0,0 +1,37 @@ +//destination buffer +b = Buffer.alloc(s,1); +c = Array.new(); + +//this patch requests a folder and will iterate through all accepted audiofiles and concatenate them in the destination buffer. It will also yield an array with the numFrame where files start in the new buffer. + +( +var tempbuf,dest=0, fileNames; + +FileDialog.new({|selection| + var total; + fileNames = PathName.new(selection[0]) + .entries + .select({|f| + [\wav, \WAV, \mp3,\aif].includes(f.extension.asSymbol);}); + total = fileNames.size() - 1; + Routine{ + fileNames.do{|f, i| + f.postln; + ("Loading"+i+"of"+total).postln; + tempbuf = Buffer.read(s,f.asAbsolutePath); + s.sync; + c = c.add(dest); + FluidBufCompose.process(s,tempbuf.bufnum,dstStartAtA:dest,srcBufNumB:b.bufnum,dstBufNum:b.bufnum); + s.sync; + b.updateInfo(); + s.sync; + dest = b.numFrames; + }; + "load buffers done".postln; + }.play; +}, fileMode:2); +) + +b.plot +c.postln +b.play diff --git a/release-packaging/ignore/Examples/nmf/JiT-NMF.scd b/release-packaging/ignore/Examples/nmf/JiT-NMF.scd new file mode 100644 index 0000000..bb137ba --- /dev/null +++ b/release-packaging/ignore/Examples/nmf/JiT-NMF.scd @@ -0,0 +1,57 @@ +s.reboot + +//this patch does just-in-time nmf processes on buffer, faking a slightly delayed real-time version of it +//what is happening: +//a circular buffer is doing a fake real time - every half second, it sends a frame to be proceesed by NMF~, requesting 3 ranks. Because this latter process is randomly seeded and not sorted, the 3 ranks are not getting similar results each time, hence the random pan + +( +b = Buffer.alloc(s,s.sampleRate * 2); +c = Buffer.new(s,0,3); +d = Buffer.new(s,0,3); +e = Buffer.read(s,File.realpath(FluidBufNMF.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Nicol-LoopE-M.wav"); +g = Bus.audio(s,1); +h = Buffer.loadCollection(s, Signal.rectWindow(22491).fade(0,440).fade(22049,22489,1,0).put(22490,0)); + +SynthDef(\becauseIcan,{arg bufnum = 0, nmfa = 0, nmfb = 0, input = 0, env = 0; + var head, head2, duration, audioin, halfdur; + duration = BufFrames.kr(bufnum) / 2; + halfdur = duration / 2; + head = Phasor.ar(0,1,0,duration); + head2 = (head + halfdur) % duration; + + // circular buffer writer + audioin = In.ar(input,1); + BufWr.ar(audioin,bufnum,head,0); + BufWr.ar(audioin,bufnum,head+duration,0); + + // cue the calculations via the language + SendReply.ar(head > 500, '/processplease',2); + SendReply.ar(head > (halfdur + 500), '/processplease',1); + + // read the 2 buffers with an envelop + Out.ar(0, Splay.ar(BufRd.ar(3,nmfa,head,0,1) * BufRd.ar(1,env,head,0,1)) + Splay.ar(BufRd.ar(3,nmfb,head2,0,1) * BufRd.ar(1,env,head2,0,1))); +}).add; + +SynthDef(\playa, { arg output = 0, bufnum = 0; + Out.ar(output,PlayBuf.ar(1,bufnum,loop:1)); +}).add; +) + +// instantiate the player +x = Synth(\playa,[\output, g.index, \bufnum, e.bufnum]); + +// instantiate the processor +y = Synth(\becauseIcan,[\bufnum, b.bufnum, \nmfa, c.bufnum, \nmfb, d.bufnum, \input, g.index, \env, h.bufnum], x, 'addAfter'); + +// instantiate the listener to cue the processing from the language side +( +w = OSCFunc({ arg msg; + if(msg[3]== 1, { + FluidBufNMF.process(s, b.bufnum, nFrames: 22500, dstBufNum: c.bufnum, rank: 3, fftSize: 1024, winSize: 512, hopSize: 256); + }, { + FluidBufNMF.process(s, b.bufnum, 22050, 22500, dstBufNum: d.bufnum, rank: 3, fftSize: 1024, winSize: 512, hopSize: 256); + });}, '/processplease', s.addr); +) + +// stop it all +b.free;c.free;d.free;e.free;f.free;g.free;w.clear;x.free; y.free; From f351ba98ecc572d9ce8528fa8d5df6548e10c988 Mon Sep 17 00:00:00 2001 From: Pierre Alexandre Tremblay Date: Thu, 31 Jan 2019 14:02:53 +0000 Subject: [PATCH 016/110] bufnmf now with updating dict example --- .../HelpSource/Classes/FluidBufNMF.schelp | 90 ++++++++++++++++++- 1 file changed, 86 insertions(+), 4 deletions(-) diff --git a/release-packaging/HelpSource/Classes/FluidBufNMF.schelp b/release-packaging/HelpSource/Classes/FluidBufNMF.schelp index 70c32dd..5c61110 100644 --- a/release-packaging/HelpSource/Classes/FluidBufNMF.schelp +++ b/release-packaging/HelpSource/Classes/FluidBufNMF.schelp @@ -111,7 +111,7 @@ RETURNS:: EXAMPLES:: STRONG::A didactic example:: - CODE:: +CODE:: ( // create buffers b = Buffer.alloc(s,44100); @@ -235,9 +235,9 @@ c.plot;x.plot; y.plot; ) :: - STRONG::Fixed Dictionnaries:: The process can be trained, and the learnt dictionaries or activations can be used as templates. +STRONG::Fixed Dictionnaries:: The process can be trained, and the learnt dictionaries or activations can be used as templates. - CODE:: +CODE:: //set some buffers ( @@ -292,4 +292,86 @@ c.play // it even null-sums {(PlayBuf.ar(2,c.bufnum,doneAction:2).sum)-(PlayBuf.ar(1,b.bufnum,doneAction:2))}.play :: - \ No newline at end of file + +STRONG::Updating Dictionnaries:: The process can update dictionaries provided as seed. + +CODE:: +( +// create buffers +b = Buffer.alloc(s,44100); +c = Buffer.alloc(s, 44100); +d = Buffer.new(s); +e = Buffer.alloc(s,513,3); +f = Buffer.new(s); +g = Buffer.new(s); +) + +( +// fill them with 2 clearly segregated sine waves and composite a buffer where they are consecutive +Routine { + b.sine2([500],[1], false, false); + c.sine2([5000],[1],false, false); + s.sync; + FluidBufCompose.process(s,srcBufNumA:b.bufnum, srcBufNumB:c.bufnum,dstStartAtB:44100,dstBufNum:d.bufnum); + s.sync; + d.query; +}.play; +) + +// check +d.plot +d.play //////(beware !!!! loud!!!) + +( +//make a seeding dictionary of 3 ranks: +var highpass, lowpass, direct; +highpass = Array.fill(513,{|i| (i < 50).asInteger}); +lowpass = 1 - highpass; +direct = Array.fill(513,0.1); +e.setn(0,[highpass, lowpass, direct].flop.flat); +) + +//check the dictionary: a steep lowpass, a steep highpass, and a small DC +e.plot +e.query + +( +// use the seeding dictionary, without updating +Routine { + FluidBufNMF.process(s, d.bufnum, dstBufNum:f.bufnum, dictBufNum: e.bufnum, dictFlag: 2, actBufNum:g.bufnum, rank:3); + s.sync; + e.query; + f.query; + g.query; +}.play +) + +// look at the resynthesised separated signal +f.plot; + +// look at the dictionaries that have not changed +e.plot; + +// look at the activations +g.plot; + +( +// use the seeding dictionary, with updating this time +Routine { + FluidBufNMF.process(s, d.bufnum, dstBufNum:f.bufnum, dictBufNum: e.bufnum, dictFlag: 1, actBufNum:g.bufnum, rank:3); + s.sync; + e.query; + f.query; + g.query; +}.play +) + +// look at the resynthesised separated signal +f.plot; + +// look at the dictionaries that have now updated in place (with the 3rd channel being more focused +e.plot; + +// look at the activations (sharper 3rd rank at transitions) +g.plot; +:: \ No newline at end of file From 6d19889ba5695614275d88268a9551997664a355 Mon Sep 17 00:00:00 2001 From: Owen Green Date: Fri, 15 Mar 2019 11:08:03 +0000 Subject: [PATCH 017/110] Formatting include/FluidSCWrapper.hpp include/SCBufferAdaptor.hpp --- include/FluidSCWrapper.hpp | 375 ++++++++++++++++++------------------ include/SCBufferAdaptor.hpp | 73 ------- 2 files changed, 188 insertions(+), 260 deletions(-) diff --git a/include/FluidSCWrapper.hpp b/include/FluidSCWrapper.hpp index 16201eb..371e64b 100644 --- a/include/FluidSCWrapper.hpp +++ b/include/FluidSCWrapper.hpp @@ -1,4 +1,4 @@ - #pragma once +#pragma once #include "SCBufferAdaptor.hpp" #include @@ -16,130 +16,142 @@ namespace fluid { namespace client { -template class FluidSCWrapper; +template +class FluidSCWrapper; namespace impl { -template struct Setter; -template struct ArgumentGetter; -template struct ControlGetter; -template using msg_iter_method = T (sc_msg_iter::*)(T); +template +struct Setter; +template +struct ArgumentGetter; +template +struct ControlGetter; +template +using msg_iter_method = T (sc_msg_iter::*)(T); //////////////////////////////////////////////////////////////////////////////////////////////////////////////// -//Iterate over kr/ir inputs via callbacks from params object +// Iterate over kr/ir inputs via callbacks from params object struct FloatControlsIter { - FloatControlsIter(float** vals, size_t N):mValues(vals), mSize(N) {} - - float next() - { - return mCount >= mSize ? 0 : *mValues[mCount++]; - } + FloatControlsIter(float **vals, size_t N) + : mValues(vals) + , mSize(N) + {} - void reset(float** vals) + float next() { return mCount >= mSize ? 0 : *mValues[mCount++]; } + + void reset(float **vals) { mValues = vals; - mCount = 0; + mCount = 0; } - + size_t size() const noexcept { return mSize; } - - private: - float** mValues; - size_t mSize; - size_t mCount{0}; + +private: + float **mValues; + size_t mSize; + size_t mCount{0}; }; -//General case -template struct GetControl +// General case +template +struct GetControl { - T operator()(World*, FloatControlsIter& controls) { return controls.next(); } + T operator()(World *, FloatControlsIter &controls) { return controls.next(); } }; -template struct ControlGetter : public GetControl +template +struct ControlGetter : public GetControl {}; -//Specializations -template struct ControlGetter +// Specializations +template +struct ControlGetter { - auto operator() (World* w, FloatControlsIter& iter) + auto operator()(World *w, FloatControlsIter &iter) { typename LongT::type bufnum = iter.next(); - return std::unique_ptr(bufnum >= 0 ? new SCBufferAdaptor(bufnum,w): nullptr); + return std::unique_ptr(bufnum >= 0 ? new SCBufferAdaptor(bufnum, w) : nullptr); } }; -template -struct ControlGetter +template +struct ControlGetter { - typename FloatPairsArrayT::type operator()(World*, FloatControlsIter& iter) + typename FloatPairsArrayT::type operator()(World *, FloatControlsIter &iter) { - return {{iter.next(),iter.next()},{iter.next(),iter.next()}}; + return {{iter.next(), iter.next()}, {iter.next(), iter.next()}}; } }; -template -struct ControlGetter +template +struct ControlGetter { - typename FFTParamsT::type operator()(World*, FloatControlsIter& iter) + typename FFTParamsT::type operator()(World *, FloatControlsIter &iter) { - return {static_cast(iter.next()),static_cast(iter.next()),static_cast(iter.next())}; + return {static_cast(iter.next()), static_cast(iter.next()), static_cast(iter.next())}; } }; //////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// Iterate over arguments in sc_msg_iter, via callbacks from params object -template Method> struct GetArgument +template Method> +struct GetArgument { - T operator()(World* w, sc_msg_iter *args) + T operator()(World *w, sc_msg_iter *args) { T r = (args->*Method)(T{0}); return r; } }; -//General cases -template struct ArgumentGetter : public GetArgument +// General cases +template +struct ArgumentGetter : public GetArgument {}; -template struct ArgumentGetter : public GetArgument +template +struct ArgumentGetter : public GetArgument {}; -template struct ArgumentGetter : public GetArgument +template +struct ArgumentGetter : public GetArgument {}; -//Specializations -template struct ArgumentGetter +// Specializations +template +struct ArgumentGetter { - auto operator() (World* w, sc_msg_iter *args) + auto operator()(World *w, sc_msg_iter *args) { typename LongT::type bufnum = args->geti(-1); - return std::unique_ptr(bufnum >= 0 ? new SCBufferAdaptor(bufnum,w) : nullptr); + return std::unique_ptr(bufnum >= 0 ? new SCBufferAdaptor(bufnum, w) : nullptr); } }; -template struct ArgumentGetter +template +struct ArgumentGetter { - typename FloatPairsArrayT::type operator()(World* w, sc_msg_iter *args) + typename FloatPairsArrayT::type operator()(World *w, sc_msg_iter *args) { - return {{args->getf(),args->getf()},{args->getf(),args->getf()}}; + return {{args->getf(), args->getf()}, {args->getf(), args->getf()}}; } }; -template struct ArgumentGetter +template +struct ArgumentGetter { - typename FFTParamsT::type operator()(World* w, sc_msg_iter *args) - { - return {args->geti(),args->geti(),args->geti()}; - } + typename FFTParamsT::type operator()(World *w, sc_msg_iter *args) { return {args->geti(), args->geti(), args->geti()}; } }; - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// -//Real Time Processor +// Real Time Processor -template class RealTime : public SCUnit +template +class RealTime : public SCUnit { using HostVector = FluidTensorView; // using Client = typename Wrapper::ClientType; @@ -148,46 +160,47 @@ public: static void setup(InterfaceTable *ft, const char *name) { registerUnit(ft, name); - ft->fDefineUnitCmd(name,"latency",doLatency); + ft->fDefineUnitCmd(name, "latency", doLatency); } - + static void doLatency(Unit *unit, sc_msg_iter *args) { - float l[] {static_cast(static_cast(unit)->mClient.latency())}; - auto ft = Wrapper::getInterfaceTable(); - + float l[]{static_cast(static_cast(unit)->mClient.latency())}; + auto ft = Wrapper::getInterfaceTable(); + std::stringstream ss; ss << '/' << Wrapper::getName() << "_latency"; std::cout << ss.str() << '\n'; - ft->fSendNodeReply(&unit->mParent->mNode,-1,ss.str().c_str() , 1, l); + ft->fSendNodeReply(&unit->mParent->mNode, -1, ss.str().c_str(), 1, l); } - - RealTime(): - mControlsIterator{mInBuf + mSpecialIndex + 1,mNumInputs - mSpecialIndex - 1}, - mParams{*Wrapper::getParamDescriptors()}, - mClient{Wrapper::setParams(mParams,mWorld->mVerbosity > 0, mWorld, mControlsIterator)} + + RealTime() + : mControlsIterator{mInBuf + mSpecialIndex + 1, mNumInputs - mSpecialIndex - 1} + , mParams{*Wrapper::getParamDescriptors()} + , mClient{Wrapper::setParams(mParams, mWorld->mVerbosity > 0, mWorld, mControlsIterator)} {} void init() { - assert(!(mClient.audioChannelsOut() > 0 && mClient.controlChannelsOut() > 0) && "Client can't have both audio and control outputs"); + assert(!(mClient.audioChannelsOut() > 0 && mClient.controlChannelsOut() > 0) && + "Client can't have both audio and control outputs"); - //If we don't the number of arguments we expect, the language side code is probably the wrong version - //set plugin to no-op, squawk, and bail; - if(mControlsIterator.size() != Wrapper::getParamDescriptors()->count()) + // If we don't the number of arguments we expect, the language side code is probably the wrong version + // set plugin to no-op, squawk, and bail; + if (mControlsIterator.size() != Wrapper::getParamDescriptors()->count()) { mCalcFunc = Wrapper::getInterfaceTable()->fClearUnitOutputs; - std::cout << "ERROR: " << Wrapper::getName() << - " wrong number of arguments. Expected " << Wrapper::getParamDescriptors()->count() << - ", got " << mControlsIterator.size() << ". Your .sc file and binary plugin might be different versions." << std::endl; + std::cout << "ERROR: " << Wrapper::getName() << " wrong number of arguments. Expected " + << Wrapper::getParamDescriptors()->count() << ", got " << mControlsIterator.size() + << ". Your .sc file and binary plugin might be different versions." << std::endl; return; } - + mInputConnections.reserve(mClient.audioChannelsIn()); mOutputConnections.reserve(mClient.audioChannelsOut()); mAudioInputs.reserve(mClient.audioChannelsIn()); - mOutputs.reserve(std::max(mClient.audioChannelsOut(),mClient.controlChannelsOut())); - + mOutputs.reserve(std::max(mClient.audioChannelsOut(), mClient.controlChannelsOut())); + for (int i = 0; i < mClient.audioChannelsIn(); ++i) { mInputConnections.emplace_back(isAudioRateIn(i)); @@ -200,10 +213,7 @@ public: mOutputs.emplace_back(nullptr, 0, 0); } - for (int i = 0; i < mClient.controlChannelsOut(); ++i) - { - mOutputs.emplace_back(nullptr, 0, 0); - } + for (int i = 0; i < mClient.controlChannelsOut(); ++i) { mOutputs.emplace_back(nullptr, 0, 0); } set_calc_function(); Wrapper::getInterfaceTable()->fClearUnitOutputs(this, 1); @@ -211,8 +221,9 @@ public: void next(int n) { - mControlsIterator.reset(mInBuf + 1); //mClient.audioChannelsIn()); - Wrapper::setParams(mParams,mWorld->mVerbosity > 0, mWorld,mControlsIterator); // forward on inputs N + audio inputs as params + mControlsIterator.reset(mInBuf + 1); // mClient.audioChannelsIn()); + Wrapper::setParams(mParams, mWorld->mVerbosity > 0, mWorld, + mControlsIterator); // forward on inputs N + audio inputs as params const Unit *unit = this; for (int i = 0; i < mClient.audioChannelsIn(); ++i) { @@ -222,33 +233,33 @@ public: { if (mOutputConnections[i]) mOutputs[i].reset(out(i), 0, fullBufferSize()); } - for(int i = 0; i < mClient.controlChannelsOut();++i) - { - mOutputs[i].reset(out(i),0,1); - } + for (int i = 0; i < mClient.controlChannelsOut(); ++i) { mOutputs[i].reset(out(i), 0, 1); } mClient.process(mAudioInputs, mOutputs); } + private: std::vector mInputConnections; std::vector mOutputConnections; std::vector mAudioInputs; std::vector mOutputs; FloatControlsIter mControlsIterator; + protected: ParameterSet mParams; - Client mClient; + Client mClient; }; //////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// Non Real Time Processor -template class NonRealTime +template +class NonRealTime { public: static void setup(InterfaceTable *ft, const char *name) { DefinePlugInCmd(name, launch, nullptr); } - NonRealTime(World *world,sc_msg_iter *args): - mParams{*Wrapper::getParamDescriptors()}, - mClient{mParams} + NonRealTime(World *world, sc_msg_iter *args) + : mParams{*Wrapper::getParamDescriptors()} + , mClient{mParams} {} void init(){}; @@ -256,20 +267,20 @@ public: static void launch(World *world, void *inUserData, struct sc_msg_iter *args, void *replyAddr) { - - if(args->tags && ((std::string{args->tags}.size() - 1) != Wrapper::getParamDescriptors()->count())) + if (args->tags && ((std::string{args->tags}.size() - 1) != Wrapper::getParamDescriptors()->count())) { - std::cout << "ERROR: " << Wrapper::getName() << - " wrong number of arguments. Expected " << Wrapper::getParamDescriptors()->count() << - ", got " << (std::string{args->tags}.size() - 1) << ". Your .sc file and binary plugin might be different versions." << std::endl; + std::cout << "ERROR: " << Wrapper::getName() << " wrong number of arguments. Expected " + << Wrapper::getParamDescriptors()->count() << ", got " << (std::string{args->tags}.size() - 1) + << ". Your .sc file and binary plugin might be different versions." << std::endl; return; } - - Wrapper *w = new Wrapper(world,args); //this has to be on the heap, because it doesn't get destoryed until the async command is done - - int argsPosition = args->count; - auto argsRdPos = args->rdpos; - Result result = validateParameters(w, world, args); + + Wrapper *w = new Wrapper( + world, args); // this has to be on the heap, because it doesn't get destoryed until the async command is done + + int argsPosition = args->count; + auto argsRdPos = args->rdpos; + Result result = validateParameters(w, world, args); if (!result.ok()) { std::cout << "ERROR: " << Wrapper::getName() << ": " << result.message().c_str() << std::endl; @@ -278,37 +289,30 @@ public: } args->count = argsPosition; args->rdpos = argsRdPos; - Wrapper::setParams(w->mParams,false, world, args); - - size_t msgSize = args->getbsize(); + Wrapper::setParams(w->mParams, false, world, args); + + size_t msgSize = args->getbsize(); std::vector completionMessage(msgSize); -// char * completionMsgData = 0; - if (msgSize) - { - args->getb(completionMessage.data(), msgSize); - } + // char * completionMsgData = 0; + if (msgSize) { args->getb(completionMessage.data(), msgSize); } - world->ft->fDoAsynchronousCommand(world, replyAddr, Wrapper::getName(), w, process, exchangeBuffers, tidyUp, destroy,msgSize, completionMessage.data()); + world->ft->fDoAsynchronousCommand(world, replyAddr, Wrapper::getName(), w, process, exchangeBuffers, tidyUp, destroy, + msgSize, completionMessage.data()); } static bool process(World *world, void *data) { return static_cast(data)->process(world); } static bool exchangeBuffers(World *world, void *data) { return static_cast(data)->exchangeBuffers(world); } static bool tidyUp(World *world, void *data) { return static_cast(data)->tidyUp(world); } - static void destroy(World *world, void *data) - { - -// void* c = static_cast(data)->mCompletionMessage; -// if(c) world->ft->fRTFree(world,c); - delete static_cast(data); - } + static void destroy(World *world, void *data) { delete static_cast(data); } protected: ParameterSet mParams; - Client mClient; + Client mClient; + private: - static Result validateParameters(NonRealTime *w, World* world, sc_msg_iter *args) + static Result validateParameters(NonRealTime *w, World *world, sc_msg_iter *args) { - auto results = w->mParams.template checkParameterValues(world, args); + auto results = w->mParams.template checkParameterValues(world, args); for (auto &r : results) { if (!r.ok()) return r; @@ -318,118 +322,121 @@ private: bool process(World *world) { - Result r = mClient.process();///mInputs, mOutputs); - - if(!r.ok()) + Result r = mClient.process(); + + if (!r.ok()) { std::cout << "ERROR: " << Wrapper::getName() << ": " << r.message().c_str(); - return false; + return false; } - + return true; } bool exchangeBuffers(World *world) { - mParams.template forEachParamType(world); -// for (auto &b : mBuffersOut) b.assignToRT(world); + mParams.template forEachParamType(world); return true; } bool tidyUp(World *world) { -// for (auto &b : mBuffersIn) b.cleanUp(); -// for (auto &b : mBuffersOut) b.cleanUp() - mParams.template forEachParamType(); + mParams.template forEachParamType(); return true; } - template + template struct AssignBuffer { - void operator()(typename BufferT::type& p, World* w) + void operator()(typename BufferT::type &p, World *w) { - if(auto b = static_cast(p.get())) - b->assignToRT(w); + if (auto b = static_cast(p.get())) b->assignToRT(w); } }; - - template + + template struct CleanUpBuffer { - void operator()(typename BufferT::type& p) + void operator()(typename BufferT::type &p) { - if(auto b = static_cast(p.get())) - b->cleanUp(); + if (auto b = static_cast(p.get())) b->cleanUp(); } }; -// std::vector mBuffersIn; -// std::vector mBuffersOut; -// std::vector mInputs; -// std::vector mOutputs; - char * mCompletionMessage = nullptr; - void * mReplyAddr = nullptr; - const char * mName = nullptr; + char * mCompletionMessage = nullptr; + void * mReplyAddr = nullptr; + const char *mName = nullptr; }; //////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// An impossible monstrosty -template class NonRealTimeAndRealTime : public RealTime, public NonRealTime +template +class NonRealTimeAndRealTime : public RealTime, public NonRealTime { static void setup(InterfaceTable *ft, const char *name) { - RealTime::setup(ft, name); - NonRealTime::setup(ft, name); + RealTime::setup(ft, name); + NonRealTime::setup(ft, name); } }; //////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Template Specialisations for NRT/RT -template class FluidSCWrapperImpl; +template +class FluidSCWrapperImpl; -template class FluidSCWrapperImpl : public NonRealTime +template +class FluidSCWrapperImpl + : public NonRealTime { public: - FluidSCWrapperImpl(World* w, sc_msg_iter *args): NonRealTime(w,args){}; + FluidSCWrapperImpl(World *w, sc_msg_iter *args) + : NonRealTime(w, args){}; }; -template class FluidSCWrapperImpl : public RealTime +template +class FluidSCWrapperImpl : public RealTime {}; //////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Make base class(es), full of CRTP mixin goodness -template -using FluidSCWrapperBase = FluidSCWrapperImpl,Params, isNonRealTime, isRealTime>; +template +using FluidSCWrapperBase = + FluidSCWrapperImpl, Params, isNonRealTime, isRealTime>; } // namespace impl //////////////////////////////////////////////////////////////////////////////////////////////////////////////// -///The main wrapper -template class FluidSCWrapper : public impl::FluidSCWrapperBase +/// The main wrapper +template +class FluidSCWrapper : public impl::FluidSCWrapperBase { public: using Client = C; using Params = P; - FluidSCWrapper() //mParams{*getParamDescriptors()}, //impl::FluidSCWrapperBase() - { impl::FluidSCWrapperBase::init(); } - - FluidSCWrapper(World* w, sc_msg_iter *args): impl::FluidSCWrapperBase(w,args) - { impl::FluidSCWrapperBase::init(); } + FluidSCWrapper() // mParams{*getParamDescriptors()}, //impl::FluidSCWrapperBase() + { + impl::FluidSCWrapperBase::init(); + } + FluidSCWrapper(World *w, sc_msg_iter *args) + : impl::FluidSCWrapperBase(w, args) + { + impl::FluidSCWrapperBase::init(); + } static const char *getName(const char *setName = nullptr) { static const char *name = nullptr; return (name = setName ? setName : name); } - + static Params *getParamDescriptors(Params *setParams = nullptr) { - static Params* descriptors = nullptr; + static Params *descriptors = nullptr; return (descriptors = setParams ? setParams : descriptors); } @@ -439,7 +446,7 @@ public: return (ft = setTable ? setTable : ft); } - static void setup(Params& p, InterfaceTable *ft, const char *name) + static void setup(Params &p, InterfaceTable *ft, const char *name) { getName(name); getInterfaceTable(ft); @@ -447,35 +454,29 @@ public: impl::FluidSCWrapperBase::setup(ft, name); } - template - static auto& setParams(ParameterSet& p, bool verbose, World* world, impl::FloatControlsIter& inputs) + template + static auto &setParams(ParameterSet &p, bool verbose, World *world, impl::FloatControlsIter &inputs) { - //We won't even try and set params if the arguments don't match - if(inputs.size() == getParamDescriptors()->count()) + // We won't even try and set params if the arguments don't match + if (inputs.size() == getParamDescriptors()->count()) p.template setParameterValues(verbose, world, inputs); return p; } - template - static auto& setParams(ParameterSet& p, bool verbose, World* world, sc_msg_iter *args) + template + static auto &setParams(ParameterSet &p, bool verbose, World *world, sc_msg_iter *args) { - p.template setParameterValues(verbose,world, args); - return p; + p.template setParameterValues(verbose, world, args); + return p; } - -// impl::ParameterSet mParams; - -// Client &client() { return mClient; } -// -//private: -// Client mClient; }; -template