Quantizing durations in patterns – .normalizeSum for durations?

Hi all,

An age-old problem that has been annoying me for a while is the one of writing a pattern and trying to manually remember if the total duration of your durations in a Pseq for example are equivalent to a certain number (let’s say 4 beats in this (bad) example):

Pbind(
\dur, Pseq([1,1/4,1/8,1/12,Rest(2)], inf) // Is this 4 beats? 
).play

It would be a more musical/performative workflow for me if I could call something equivalent to .normalizeSum but instead of normalizing all items to 1.0 (and then what to do about rests??), I want all durations (including rests) to be scaled.

I couldn’t immediately find a good function for this in the class library, so I wrote a first attempt:

+ Array{
    // return the total duration of an array of numbers and Rests
    totalDuration{
         ^this.sanitizeRests.sum;
    }

    sanitizeRests{
        ^this.collect{|x| x.isRest.if({x.dur}, x) };
    }

    // FIXME: WORK IN PROGRESS
    // FIXME: How to make it round properly and not return zeroes?
    // Take an array of durations / Rests and normalize their total duration (for Pseq etc)
    // Not 100% accurate but mostly works
    normalizeDurations{|sumDurations=8.0, roundTo|
        var returnArray = this;

        // Get the indices of all Rests
        var areRests = this.collect{|x| x.isRest};

        // Convert to durations
        returnArray = returnArray.sanitizeRests();

        // Normalize and scale
        returnArray = returnArray.normalizeSum().collect{|x| x * sumDurations };

        // Round
        roundTo.notNil.if({
            returnArray = returnArray.collect{|x|
                // The rounding algos expect x to to not be 0 (and so does \dur in Pbind)
                if(x == 0.0, {
                    x = 0.00001
                });

                x.roundUp(roundTo).postln;
            };
        });

        // Convert rests back to rests
        returnArray = returnArray.collect{|x, index| areRests[index].if({Rest(x)}, x)};

        ^returnArray;
    }
}

With the example above:

Pbind(
\dur, Pseq([1,1/4,1/8,1/12,Rest(2)].normalizeDurations(4), inf) // Is this 4 beats? Yes it is 
).play

Now, this seemingly works OK, but there is a problem, if we start rounding/quantizing the durations, the returned array will always be too long.

a = [1,1/4,1/8,1/12,Rest(2)].normalizeDurations(4, roundTo: 0.125).postln;
a.totalDuration.postln;

This leads to the big question: What to do about this? Do we shave off bits of duration from the Rests? Do we scale it back and forth recursively until it snaps into place (this could be very taxing on the CPU)? Any advice?

1 Like

Isn’t the following easier?

If you put the rest in the notes instead of the durations, you can use normalizeSum and then multiply by the desired #beats, e.g.

(
s.waitForBoot {
	SynthDef(\beat, {
		|out= 0, amp=0.01|
		var sig = Blip.ar(55, 1);
		var env = amp*EnvGen.ar(Env.perc(0.01, 0.5));
		Out.ar(out, env*sig!2);
	}).add;
	
	s.sync;
	
	a = Pbind(\instrument, \default,
		\midinote, Pseq([60, 67, Rest(), 69, 54], inf),
		\dur, Pseq([1, 2, 0.5, 6, 1].normalizeSum*4, inf)  // total of four beats
	);
	
	b = Pbind(\instrument, \beat,
		\dur, Pseq([1], inf)
	);
	
	Ppar([a, b]).play;	
})

Edit: that doesn’t address the quantization issues, which I guess was the purpose of the question.

But there’s no way (in general) to quantize all durations without loss of total duration. So I can think of only two main strategies (that probably says more about me than about the problem:)) :slight_smile:

  1. After quantization you could again normalize and multiply desired #beats to shave off (or add) something of every duration, but then you again end up with something non-quantized.

  2. So I guess shaving off something from rests only is an alternative (but only you have enough rests in the piece to make it possible), or alternatively you could insert a small rest to fill a gap.

1 Like

What about something like that?

a=[1, 1/4, 1/8, 1/12, Rest(2)].collect{|it| if (it.isRest) {['rest', it.dur]} {['note', it]}}.flop;
(
p = Pbind(
    \dur, Pseq(a[1].normalizeSum*4, inf),
    \type, Pseq(a[0], inf)
).play;
)

Edit: Sorry, I see what kind of issue you have to face.
Here is a good ref to know:
David Goldberg. What Every Computer Scientist Should Know About Floating- Point Arithmetic. Numerical Computation Guide, July 2001, pp. 171 to 264.

To manage this, I would suggest some kind of rounded function as follow:

~round={|a, n=3| ((a*(10**n.asInteger)).round)/(10**n.asInteger)};

Hope I am not too off topic! :grimacing:

Re-edit: To manage the zero value just remove it from the array after rounding, or increase the value of n.

Last-edit: To scale your durations to the nearest discrete interval, you have no choice I think, you have to implement some kind of quantizer that fits your expectation.
For more about quantization, see for instance: Desain, P., & Honing, H. (1992). The quantization problem: traditional and connectionist approaches. In M. Balaban, K. Ebcioglu, & O. Laske (eds.).

P.S. I was a bit off topic! :thinking:

1 Like