Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 28 additions & 15 deletions doc/BufNNDSVD.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,50 +3,63 @@
: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:

Fraction (0 to 1) of information preserved in the decomposition

: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.
32 changes: 23 additions & 9 deletions example-code/sc/BufNNDSVD.scd
Original file line number Diff line number Diff line change
@@ -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
)
::