diff --git a/doc/BufNNDSVD.rst b/doc/BufNNDSVD.rst index bb95b5e..46a451c 100644 --- a/doc/BufNNDSVD.rst +++ b/doc/BufNNDSVD.rst @@ -3,32 +3,32 @@ :sc-categories: FluidManipulation :sc-related: Classes/FluidBufNMF :see-also: -:description: - Find Initial Bases and Activations for FluidBufNMF via Non-Negative Double Singular Value Decomposition . - - See http://nimfa.biolab.si/nimfa.methods.seeding.nndsvd.html - +:description: Find Initial Bases and Activations for BufNMF +:discussion: + BufNNDSVD uses Nonnegative Double Singular Value Decomposition which can help decide how to initialise BufNMF, by suggesting how many components to request (and what bases and activations to seed) in order to account for a certain percentage of the variance in a buffer. In general, using this process to seed a BufNMF decomposition should substantially increase the speed with which BufNMF converges and avoid especially poor local minima. + + See http://nimfa.biolab.si/nimfa.methods.seeding.nndsvd.html and https://www.sciencedirect.com/science/article/abs/pii/S0031320307004359 for more info. :control source: - The index of the buffer to use as the source material to be decomposed through the NMF process. The different channels of multichannel buffers will be processing sequentially. + The |buffer| to analyse and suggest a number of components for. :control bases: - The index of the buffer where the different bases will be written to and/or read from: the behaviour is set in the following argument. + The |buffer| where the bases will be written to. These are suggested seed for a BufNMF process. The number of bases (i.e., channels) in this buffer when the process is complete is the number of components needed to cover the requested percentage of variance in the buffer. :control activations: - The index of the buffer where the different activations will be written to and/or read from: the behaviour is set in the following argument. + The |buffer| where the activations will be written to. These are suggested seed for a BufNMF process. The number of bases (i.e., channels) in this buffer when the process is complete is the number of components needed to cover the requested percentage of variance in the buffer. :control minComponents: - Minimum number of estimated components + Minimum number of estimated components to return (minimum number of channels in ``bases`` |buffer| when the process is complete) :control maxComponents: - Maximum number of estimated components + Maximum number of estimated components to return (maximum number of channels in ``bases`` |buffer| when the process is complete) :control coverage: @@ -36,17 +36,30 @@ :control method: - The method used for the decomposition. Options are: + The method used to account for certain values before processing. Zeros in the matrix will remain zero when "updated" because the updates are being multiplied by a scalar, therefore it may be useful to change any zeros to something else before the process. Options are: + + :enum: + + :0: + **NMF-SVD** Nonnegative Double Singular Value Decomposition where any negative values are first converted to their absolute value. This is likely to be quicker than the other options, but less rigorous. + + :1: + **NNDSVDar** Nonnegative Double Singular Value Decomposition where any elements that are zero are first filled with a random value between 0 and the ``average * 0.01`` (essentially small random values). This may be slightly faster but slightly less accurate than other options. + + :2: + **NNDSVDa** Nonnegative Double Singular Value Decomposition where any elements that are zero are first filled with the average value. + + :3: + **NNDSVD** Nonnegative Double Singular Value Decomposition where values are not changed according to any criteria. This promotes sparse results from the subsequent NMF (because, with multiplicative updates, zeros remain zeros). This may or may not be desirable (not least because sparsity implies the need for more components, but also the specific domain might imply that reasonable decomposition just isn't going to be sparse). :control windowSize: - The window size. As spectral differencing 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 window size. 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 :control hopSize: - The window hop size. As sinusoidal estimation relies on spectral frames, we need to move the window forward. It can be any size but low overlap will create audible artefacts. The -1 default value will default to half of windowSize (overlap of 2). + The window hop size. It can be any size but low overlap will create audible artefacts. The -1 default value will default to half of windowSize (overlap of 2). :control 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 -1 default value will use the next power of 2 equal or above the highest of windowSize and (bandwidth - 1) * 2. - + 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 -1 default value will use the next power of 2 equal or above the highest of windowSize and (bandwidth - 1) * 2. diff --git a/example-code/sc/BufNNDSVD.scd b/example-code/sc/BufNNDSVD.scd index c892ab1..0dab4b0 100644 --- a/example-code/sc/BufNNDSVD.scd +++ b/example-code/sc/BufNNDSVD.scd @@ -1,31 +1,45 @@ code:: + ( -b = Buffer.read(s,FluidFilesPath("Nicol-LoopE-M.wav")); +~src = Buffer.read(s,FluidFilesPath("Nicol-LoopE-M.wav")); ~bases = Buffer.new(s); ~activations = Buffer.new(s); ~resynth = Buffer.new(s); ) -//how many bases do I need to decompose the buffer with 90% accuracy +//how many bases do I need to decompose the buffer while accounting for 90% of the variance? ( Routine{ - FluidBufNNDSVD.process(s, b, ~bases, ~activations, coverage: 0.9, method: 1).wait; + FluidBufNNDSVD.process(s, ~src, ~bases, ~activations, coverage: 0.9, method: 1).wait; "% bases".format(~bases.numChannels).postln; }.play; ) -//check how many bases we are returned: - -//try the same process with less accuracy +//try the same process with less of the variance preserved ( Routine{ - FluidBufNNDSVD.process(s, b, ~bases, ~activations, coverage: 0.5).wait; + FluidBufNNDSVD.process(s, ~src, ~bases, ~activations, coverage: 0.5).wait; "% bases".format(~bases.numChannels).postln; }.play ) +// peek at the bases +~bases.plot; + +// peek at the activations +~activations.plot; + //use the bases to run NMF on -FluidBufNMF.process(s, b, resynth: ~resynth, bases: ~bases, activations: ~activations,actMode: 2, components: ~bases.numChannels, action: {\done.postln;}) -{PlayBuf.ar(~resynth.numChannels, ~resynth)[2]}.play +FluidBufNMF.process(s, ~src, resynth: ~resynth, resynthMode:1, bases: ~bases, basesMode:1, activations: ~activations, components: ~bases.numChannels, action: {"done".postln;}) + +// peek at the components +FluidWaveform(~resynth,bounds:Rect(0,0,1000,1000)); + +// listen to component index 2: +( +{ + PlayBuf.ar(~resynth.numChannels, ~resynth)[2] +}.play +) ::