musikinformatik / Steno

Concatenative little metalanguage for live coding
GNU General Public License v2.0
65 stars 7 forks source link

towards fixing fading #17

Closed LFSaw closed 7 years ago

LFSaw commented 7 years ago

My test file is here: https://gist.github.com/LFSaw/aa60234ddb04ae4c5348112ecab170d7

This PR is big and by far not ready to merge (I think). It basically are two commits, the rest are merge artefacts from branches I already proposed in other PRs (#14 #15 #16).

52aaf68

To fix fading behaviour to sound reasonable, I realised that an additional numChannels-sized bus is needed: I called it Steno:fadeBus (that's the index, no bus object used, I'm open for naming ideas, but I;d like to keep it reasonably short for the sake of OSC communication).

When a filter is replaced (DiffString:swapFunc), it sends an arg \replacement, 1 which indicates that this filter is a replacement for an old one. This triggers that it reads its signal from fadeBus during the fade and switches later to normal behaviour. All this is defined in `StenoSignal:filterInput.

Accrodingly, all Synths need to write (i.e. ReplaceOut) to that bus (compare lower third of each, StenoSignal:filterInput and StenoSignal:quelleInput).

4fb2e1c

Since also the Synths for []{}() need to write there, I made them use the StenoSignal class.


missing

telephon commented 7 years ago

what happens if you crossfade into a new set of synths while you are in the process of a crossfade already?

telephon commented 7 years ago

ah I see now how the multiple crossfade works.

telephon commented 7 years ago

every synth has its own, and cleans up after itself .

telephon commented 7 years ago

OK, I've had a closer look. I wonder: why is the writing to the fadeBus done in the method that constructs the filter input? Shouldn't it be the method that constructs the filter output?

I understand that we need a place to have the original input signal for all versions of one single cross fading synth.

LFSaw commented 7 years ago

why is the writing to the fadeBus done in the method that constructs the filter input? Shouldn't it be the method that constructs the filter output?

because it writes the unaltered signal rather than the altered signal. AFAI remember (late atm.), the filter output method does not have a link to that signal anymore.

telephon commented 7 years ago

ok, makes sense. After some thinking I also see that other way round would be more complicated.

telephon commented 7 years ago

Although, just trying to reconsider carefully:

The current problem is only that filters overwrite their input signal (this is only true for sequential filters, not for parallel ones).

So if every synth would write its output also to the fadeBus, it would be available, wouldn't it?

telephon commented 7 years ago

I think if you reconstruct the fadeBus to write / read the other way round, the bus logic should be done here:

getBusArgs { |readIndex, writeIndex, dryReadIndex, through, argumentIndex|
        var readBus = busIndices.clipAt(readIndex);
        var writeBus = busIndices.clipAt(writeIndex + (argumentIndex ? 0));
        var dryReadBus = busIndices.clipAt(dryReadIndex);
        ^[\in, readBus, \out, writeBus, \dryIn, dryReadBus, \through, through]
    }

something like this:

getBusArgs { |readIndex, writeIndex, dryReadIndex, through, argumentIndex|
        var readBus = busIndices.clipAt(readIndex);
        var writeBus = busIndices.clipAt(writeIndex + (argumentIndex ? 0));
        var dryReadBus = busIndices.clipAt(dryReadIndex);
        var fadeBus = fadeBusses.clipAt(argumentIndex); // <----- maybe like this?
        ^[\in, readBus, \out, writeBus, \dryIn, dryReadBus, \through, through]
    }

or maybe we really only need a single fadeBus, then (having added fadeBus to the instance variables of StenoStack):

getBusArgs { |readIndex, writeIndex, dryReadIndex, through, argumentIndex|
        var readBus = busIndices.clipAt(readIndex);
        var writeBus = busIndices.clipAt(writeIndex + (argumentIndex ? 0));
        var dryReadBus = busIndices.clipAt(dryReadIndex);
        ^[\in, readBus, \out, writeBus, \dryIn, dryReadBus, \through, through, \fadeBus, fadeBus]
    }
LFSaw commented 7 years ago

So, I did a drawing that, I hope, reflects the intended behaviour of fading in steno.

image

[c] shows the current implementation, where fadeBus keeps the original, unprocessed data.

I'll now try to mimic your suggestion...

telephon commented 7 years ago

I think the right diagram isn't correct yet. Dependent on different situations, read and write (you marked as inBus and outBus) are the following:

|              | read | write | dry-read | fadeBus |
| serial    |  0     |  0       | 0            | ? |
| parallel | -1     | 0        | -1           |  ? | 

But I suppose the situation we need to cover is only the serial one.

LFSaw commented 7 years ago

hmm... I was referring to the actual implementation and the names of the buses in the code... I also don't understand what you mean by 0/-1/?

telephon commented 7 years ago

ah sorry, also now I understand [c] is the picture on the right.

0 means the current bus offset -1 means the previous one.

But never mind, it'll be easier with the alternate solution anyhow.

LFSaw commented 7 years ago
image
telephon commented 7 years ago

OK, here is a formula. Let's suppose that we have a tailBus for all the filter tails, and normally write and read from outBus. All use ReplaceOut.

(
var outbus, tailBus;

// normal = lastSynth, doesn't matter. New synths are always added after, as "normal".
// this works, because it also always writes zero to the tailBus to clean up.

var normal = { |oldSignal, filteredSignal, allTails|

    outbus =  [oldSignal * 0, filteredSignal * 1, allTails * 1].sum; // assuming that tails are cleaned up by last synth.
    tailBus = [oldSignal * 0, filteredSignal * 0, allTails * 0].sum

};

// just fading out differs.

var fadingOutReplacement = { |oldSignal, filteredSignal, allTails|

    outbus =  [oldSignal * 1, filteredSignal * 0, allTails * 0].sum;
    tailBus = [oldSignal * 0, filteredSignal * 1, allTails * 1].sum

};

var fadingOut = { |oldSignal, filteredSignal, allTails|

    outbus =  [oldSignal * 0.5, filteredSignal * 1, allTails * 0].sum; // 0.5 means a fade-in
    tailBus = [oldSignal * 0, filteredSignal * 0, allTails * 0].sum

};
)

Note that when fading out in a filter the input signal is x-faded out (not featured here) so that automatically the filteredSignal should reach silence, even if it is filteredSignal * 1 here.

telephon commented 7 years ago

One problem is: how do we know what the last synth is? There is no notification about that a synth has indeed ended.

That is when to use fadingOutReplacement and when fadingOut?

LFSaw commented 7 years ago
image

I am thinking that this is what we're intending to implement. first row is a "replacement-group" for Filter, 2nd row is a replacement group for Quellen.

This means that all synths are instantiated in the form of the rightmost appearance (called A ) and, when being replaced, get a "replace" message which switches them to the layout in the left versions (called A' resp. A'').

telephon commented 7 years ago

seems like the most uniform solution between quelle and filter, yes. Also good that the available input for quelle is the same.

it would be optimal to reformulate the whole diagram in a way that all outs are ReplaceOut. Then it will be easier to implement in the current context.

LFSaw commented 7 years ago

I started implementing the collectTails scheme in this branch: https://github.com/tai-studio/Steno/tree/topic/fix-fading-collectTails

with https://github.com/tai-studio/Steno/commit/4be731f10d9b2152a1cedb4a2064ff57b407d97f I implemented the fading mechanism for filters (quellen do not work yet).

(
s.waitForBoot{
    t = t ?? {Steno(2, true).push};

    // non-standard filter (not using incoming signal)
    t.filter(\f, { |in, controls| SinOsc.ar(ExpRand(1000, 2000), 0, Decay.ar(Impulse.ar(10), 0.01))  * controls[\env] });
    t.filter(\g, { |in, controls| SinOsc.ar({ExpRand(400, 800)}!2)  * controls[\env] });

    // standard filter (use incoming signal)
    t.filter(\h, { |in, controls| RHPF.ar(in, Rand(1000, 2000), 0.1) });
    t.filter(\i, { |in, controls| RLPF.ar(in, Rand(4000, 8000), 0.1) });

    t.setGlobal(\attack, 5, \fadeTime, 5); // set fadeTime and attack to 5 seconds
    t.quant_(0.5);  // set quant to synchronise pulses
    s.dumpOSC(1)    // see messages that are sent
}
)

// single filter
-- f
-- g
-- f

// smooth transition

-- !f
s.queryAllNodes
// >>  wrong order

// with "normal" filter (h and i)
-- fh
-- fi
-- fh

-- !fh
s.queryAllNodes
// >>  wrong order

// since f and g swallow their input, only g is heard
-- fg
// set mix
t.set(\g, \mix, 0.9) // both
t.set(\g, \mix, 0.3) // both
t.set(\g, \mix, 0) // only f

-- fh
LFSaw commented 7 years ago

it would be optimal to reformulate the whole diagram in a way that all outs are ReplaceOut. Then it will be easier to implement in the current context.

They are, or?

telephon commented 7 years ago

writeToBus now has an Xout. My formulas above just do all the work outside this method. But never mind for now.

LFSaw commented 7 years ago

I am sorry, I just do not understand them... and where they should go... I like the idea to "just" have one signal passed to writeToBus which is treated differently, depending on where it goes...

LFSaw commented 7 years ago

added implementation for quellen in https://github.com/tai-studio/Steno/tree/topic/fix-fading-collectTails

LFSaw commented 7 years ago

I created a separate PR ( #18 ) for the alternative scheme.

LFSaw commented 7 years ago

This branch is obsolete and superseded by #18