SuperCollider CLASSES (extension)

CircleRamp
ExtensionExtension

circular linear lag
Inherits from: Filter : PureUGen : UGen : AbstractFunction : Object

Description

This is just like Ramp, but always takes the shortest way around a defined circle, wrapping values where appropriate. This can be useful when smoothing panning signals for speaker rings, for instance in Vector Base Amplitude Panning.

Class Methods

*ar (in: 0, lagTime: 0.1, circmin: -180, circmax: 180, mul: 1, add: 0)

*kr (in: 0, lagTime: 0.1, circmin: -180, circmax: 180, mul: 1, add: 0)

Arguments:

in

The signal to be smoothed.

lagTime

Lag time in seconds. The default is 0.1.

circmin

The lower wrap value. The default is -180.

circmax

The upper wrap value. The default is 180.

Inherited class methods

Instance Methods

Inherited instance methods

Examples

(
var speakerList, x=200, y=150, targx=200, targy=150;
var atorad = (2pi) / 360, rtoang = 360.0 / (2pi);
var targRotate, actRotate, targPoint, actPoint;
var maxShiftPerFrame = 20, frameInterval = 0.01;
var resched = false, count = 0;
var panBus, widthBus, recButton;
var a, b, c, e;

maxShiftPerFrame = maxShiftPerFrame * atorad;
actPoint = Point(x, y) - Point(200, 200);
panBus = Bus.control;
widthBus = Bus.control.set(60);

w = GUI.window.new("5.1 Panner", Rect(128, 64, 400, 450)).front;
w.view.background_(Color.grey(0.3));
w.view.decorator = FlowLayout(w.view.bounds);
speakerList = [[-30, "L"], [30, "R"], [0, "C"], [-110, "Ls"], [110, "Rs"]];
c = GUI.userView.new(w,Rect(0, 0, 400, 380));
c.canFocus = false;

c.drawFunc = {
    Color.grey(0.8).set;
    // draw the speaker layout
    GUI.pen.translate(200,200);
    ((actPoint.theta + (0.5pi)).wrap2(pi) * rtoang).round(0.01).asString.drawCenteredIn(Rect.aboutPoint(0@170, 30, 10), GUI.font.new("Arial", 10), Color.grey(0.8));
    GUI.pen.strokeOval(Rect.aboutPoint(0@0, 150, 150));
    GUI.pen.rotate(pi);
    speakerList.do({|spkr|
        GUI.pen.use({
            GUI.pen.rotate(spkr[0] * atorad);
            GUI.pen.moveTo(0@170);
            GUI.pen.strokeRect(r = Rect.aboutPoint(0@170, 30, 10));
            if(spkr[0].abs < 90, {
                GUI.pen.use({
                    GUI.pen.translate(0, 170); 
                    GUI.pen.rotate(pi);
                    spkr[1].drawCenteredIn(Rect.aboutPoint(0@0, 30, 10), 
                        GUI.font.new("Arial", 10), Color.grey(0.8));
                });
            },{ 
                spkr[1].drawCenteredIn(r, GUI.font.new("Arial", 10), Color.grey(0.8));
            });
        });
    });

    GUI.pen.moveTo(0@0);
    
    // draw the pan point
    GUI.pen.rotate(actPoint.theta + 0.5pi);
    targPoint = Point(x, y) - Point(200, 200);
    // trunc to avoid loops due to fp math
    targRotate = (targPoint.theta - actPoint.theta).trunc(1e-15);
    // wrap around
    if(targRotate.abs > pi, {targRotate = (2pi - targRotate.abs) * targRotate.sign.neg}); 
    actRotate = targRotate.clip2(maxShiftPerFrame).trunc(1e-15);
    actPoint = actPoint.rotate(actRotate);
    GUI.pen.rotate(actRotate);
    GUI.pen.lineTo(0@150);
    GUI.pen.stroke;
    GUI.pen.fillOval(Rect.aboutPoint(0@150, 7, 7));
    GUI.pen.addWedge(0@0, 140, neg(e.value * 0.5) * atorad + 0.5pi, e.value * atorad);
    GUI.pen.stroke;
    Color.grey(0.8).alpha_(0.1).set;
    GUI.pen.addWedge(0@0, 140, neg(e.value * 0.5) * atorad + 0.5pi, e.value * atorad);
    GUI.pen.fill;
    
    if((actRotate.abs > 0), {AppClock.sched(frameInterval, {w.refresh})}, {count = 0;});
    if(count%4 == 0, {panBus.set((actPoint.theta + (0.5pi)).wrap2(pi) * rtoang)});
};
c.mouseTrackFunc_({|v,inx,iny| x = inx; y = iny; w.refresh;});
c.mouseBeginTrackFunc_({|v,inx,iny| x = inx; y = iny; w.refresh;});
e = GUI.ezSlider.new(w, 380@20, "Stereo Width", [0, 180].asSpec, {arg ez; widthBus.set(ez.value); w.refresh}, 60);
e.labelView.setProperty(\stringColor,Color.grey(0.8));

w.refresh;

// VBAP

a = VBAPSpeakerArray.new(2, speakerList.collect(_.first));
b = Buffer.loadCollection(s, a.getSetsAndMatrices;);

SynthDef('VBAP 5 chan', { |azi = 0, ele = 0, spr = 0, width = 60, vbapBuf|
var panned, source;
source = SinOsc.ar([440, 660], 0, Decay2.ar(Impulse.ar([1, 0.9]), 0.1, 0.2));
azi = azi.circleRamp;
panned = VBAP.ar(5, source, vbapBuf, [azi - (0.5 * width), azi + (0.5 * width)], ele, spr);
// 'standard' channel order for 5.1
[0, 1, 2, 4, 5].do({arg bus, i; Out.ar(bus, panned[0][i])});
[0, 1, 2, 4, 5].do({arg bus, i; Out.ar(bus, panned[1][i])});

}).play(s, [vbapBuf: b.bufnum, azi: panBus.asMap, width: widthBus.asMap]);

)

UGen method

CircleRamp also defines an extension method for UGen which has the same defaults listed above.

 circleRamp (lagTime, circmin, circmax)