Trouble with Pdef and Ppar

Im sure this is a simple one (noob) sorry

Can anyone tell me why this only plays the last Pdef?
Im using the package Super Clean, I just want to chain independent Pdefs. (to play at once together). But be able to be updated JitLib style. You have to format the Pdef with a Pbind this way for Super Clean. Is there something else I should use other than Ppar? Or another format that is just better? Thank you!

(
var a, b, c, d;

a = Pdef(\0,

    Pbind(*[

        type: \cln,

        snd: \mmd,

        num: 0,

        dur: Pseq([0.3,0,0,0.5,0,0,0,0.5],inf),

        amp: Pseq([1,0,0,1,0,0,0,1],inf),

    ])

);

b = Pdef(\1,

    Pbind(*[

        type: \cln,

        snd: \mmd,

        num: 1,

        dur: Pseq([0.3,0,0,0.5,0,0,0,0.5],inf),

        amp: Pseq([1,0,0,0.5,0,0,0,1],inf),

    ])

);

c = Pdef(\2,

    Pbind(*[

        type: \cln,

        snd: \mmd,

        num: 2,

        dur: Pseq([0.3,0,0,0.5,0,0,0,0.5],inf),

        amp: Pseq([1,0,0,0.5,0,0,0,1],inf),

    ]);

);

d = Pdef(\3,

    Pbind(*[

        type: \cln,

        snd: \mmd,

        num: 3,

       dur: Pseq([0.3,0,0,0.5,0,0,0,0.5],inf),

        amp: Pseq([1,0,0,0.5,0,0,0,1],inf),

    ])

);
Ppar([a,b,c,d]).play;
)

It’s definitely generating events for all 4 Pdefs, so that isn’t the problem.

BTW why do you have zero durations within one Pbind?

hjh

I don’t know. It’s all percussion with Super Clean. I don’t know the proper way to trigger samples in Super Clean. 0 is meant to be an empty space until the next event but I agreee it doesn’t make sense. . I just don’t know how to write that. Like on a standard drum machine, I need to know the best way to write this out so there is space between notes. Just a simple 4 on the floor pattern in 16 steps would be illuminating. Thank you

Im also curious how you would trigger a Playbuf that is using a drum sample with Patterns? With the freq arg or trig? How is a melodic sample triggered by patterns in a Playbuf? With the freq arg in the Playbuf as well? Does sending freq numbers automatically start a Playbuf? If so, That begs the question, what do you send a Playbuf if you want the frequency to stay the same for a percussion sample? Im sure this is noob stuff. Im sorry Im still reading the sc docs. Obviously

dur is duration until next note. So for a simple drum machine, set dur to a constant value and then adjust other parameters (setting amp to 0 as you are doing is one way… Or specifying Rest())

Do the other Pbinds make sound when you play them outside a Ppar?

I already gave you an example of how to do this here:

I’ll try it later. It’s kind of a Super Clean specific thing. I guess.

I want to make a little gui with Multisliderview to bang out quick beats for four instruments. bass drum, snare, hat, etc. I don’t know how the Mutlislider should treat the Array. If it is flat that is zero amplitude, But it’s also a time. A set time that plays nothing. I want to adjust the Multisliderview to any length. (within reason) and somehow calculate the zero amplitude (flat) slider as maybe a Rest. So I could have 16 sliders for one Playbuf/Istrument. and 64 slider for the hat, Poly rhythms basically. All cycling within the same time frame of a 4 bar 16th note beat as the default. Basically an 808 with the ability to make any instrument (playbuf) play 32 or 64th notes. Then print out an array that plays it all in the Super Clean scheme. I just have to figure out what 40 flat sliders looks like.I don’t want a huge Array, perhaps I need to calculate that time and make that a Rest? Or is there a more clever way within the pattern Class? This is what Im going for, BUT

If someone could show me the proper way to do a four on the floor beat, out of 16 notes for a 909 bass drum sample using a simple Playbuf Synthdef and Patterns, It would help a lot

Then this is step one to work out.

It might help you to have a roadmap for approaching this problem:

  1. How to trigger a sample.
  2. How to trigger samples in rhythm.
  3. How to control this from a GUI.

Part of the difficulty here is that you’re trying to do everything all at once – but it really is a sequence of problems. Part of the discipline of programming is knowing when to stick with one problem until it’s solved, and not jump ahead prematurely.

1. How to trigger a sample.

Superclean seems to be based on an event type. An event type is a way of interpreting data in an event. So the first step in working with an unfamiliar event type is to read whatever documentation is available, and write events to test their behavior.

My time is limited this morning and the Superclean documentation seems a bit opaque to me, so I’m not going to be able to do this for you right now. It looks like at minimum you need snd (sample folder) and num (sample index).

(type: \cln,
	snd: "... the folder..." /* in what format, I have no idea */ ,
	num: 0
).play;

If that doesn’t work, then you would need to change parameter values or perhaps at other parameters from the list at https://raw.githubusercontent.com/danielmkarlsson/SuperClean/main/superclean-parameters.scd

After you know which event parameters you need, then you can start writing your Pbind. Not before.

2. How to trigger samples in rhythm.

That’s the Pbind’s job. If you use the *[] trick (which the Superclean examples do), then you can copy/paste the structure of your test event.

Pbind(*[
	type: \cln,
	snd: "... the folder..." /* in what format, I have no idea */ ,
	num: 0
])

Now… for these parameters, what is the same and what is changing? For parameters that never change, you can just write the value. Note that “simple 4 on the floor pattern in 16 steps” means that every step is 0.25 beats so you’d just write \dur, 0.25.

You want a difference between notes and rests. Easy way is silent notes (note, if amp 1 is too loud, you could multiply Pseq by an attenuation factor: \amp, 0.1 * Pseq([ ... ], inf):

Pbind(*[
	type: \cln,
	snd: "... the folder..." /* in what format, I have no idea */ ,
	num: 0,
	dur: 0.25,
	amp: Pseq([1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0], inf)
])

This will play a synth even when it’s silent. To play only sounding notes, convert silent events into rests. This is one way; there are others.

Pbind(*[
	snd: "... the folder..." /* in what format, I have no idea */ ,
	num: 0,
	dur: 0.25,
	amp: Pseq([1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0], inf),
	type: Pif(Pkey(\amp) |==| 0, \rest, \cln),
])

3. How to control this from a GUI.

I’m afraid I’m out of time for the moment (already wrote a lot). Basically the design would be that the pattern should read amp values from an array, and the GUI changes values in that array.

hjh

1 Like

Thank you so much,Beyond helpful.

I was going the wrong way with super clean. I was using dur to initiate samples. amp, makes it much better.

Right , the last bit of code, is there a simple way to say, if 5 consecutive 0, 0, 0, 0, 0 or greater happen, condense it to… if dur is 0.25, Pseq(Rest(1.25))? Or whatever the amount of consecutive 0’s add up to in time.
Or maybe something better? To avoid huge arrays , basically. And have all patterns fit within the same 4 bars,

So I’d have 4 beats in 16 steps of a bass drum, then and some really quick ghost note hats interspersed within that time frame. This is easily done on say an sp1200. You can have different pads at different note values and change them as you wish within 4 bars.
I imagine this is much more tricky, but maybe not.
Again, Thank you very much.

This is what im going for

I just sold an sp1200. And it was so quick to make beats banging on the pads. Then you can change the time signature of a track at will and enter 64 th notes. For 64 th notes mixed with 16th notes or whatever, that has presented a huge challenge in supercollider cause it creates huge arrays if you do it brute force , and not some genius level of coding.

Here is the final one. Im just playing around with ideas for input control. If you use Super Clean. Its fun. Curious what other ideas people have. I’ll post this to sccode, Just for fun. This has buttons to add basic beats. And it works

// Initialize SuperClean if it doesn't exist
~clean = ~clean ?? { SuperClean(2, s) };


s.waitForBoot {
    var window, sliders, buttons, patterns, pdefs;
    var numTracks = 4;
    var numSteps = 16;
    var playing = false;
    var currentStep = 0;
    var stepIndicators;

    // Initialize SuperClean if it doesn't exist
    ~clean = ~clean ?? { SuperClean(2, s) };
    ~clean.loadSoundFiles("/Users/ss/Documents/mmd");
    s.sync;
    "SuperClean initialized".postln;

    // Initialize patterns and Pdefs
    patterns = Array.fill(numTracks, { Array.fill(numSteps, 0) });
    pdefs = numTracks.collect({ |i|
        Pdef(("track" ++ i).asSymbol,
            Pbind(*[
                type: \cln,
                snd: \mmd,
                num: i % 4, // Ensure we only use existing samples (0 to 3)
                dur: 0.15,
                sustain: 1,
                rel: 0.9,
                amp: Pfunc {
                    var amp = patterns[i][currentStep];
                    amp
                } * 1.5,
                type: Pif(Pfunc { patterns[i][currentStep] == 0 }, \rest, \cln),
            ])
        )
    });

    // Create window
    window = Window("Drum Sequencer", Rect(100, 100, 581, 500)).front;

    // Function to update pattern
    ~updatePattern = { |trackIndex|
        patterns[trackIndex] = sliders[trackIndex].value.collect { |v|
            if(v < 0.01) { 0 } { v }  // Convert very small values to 0
        };
    };

    // Create sliders
    sliders = numTracks.collect({ |i|
        MultiSliderView(window, Rect(10, 10 + (i * 100), 220, 60))
        .size_(numSteps)
        .value_(patterns[i])
        .action_({ |sl|
            ~updatePattern.(i);
        });
    });

    // Create step indicators
    stepIndicators = numTracks.collect({ |i|
        StaticText(window, Rect(10, 75 + (i * 100), 280, 20))
        .string_("Current step: 0")
        .align_(\center);
    });

    // Create erase buttons
    buttons = numTracks.collect({ |i|
        Button(window, Rect(10, 95 + (i * 100), 80, 20))
        .states_([["Erase Track " ++ (i+1)]])
        .action_({
            patterns[i] = Array.fill(numSteps, 0);
            sliders[i].value_(patterns[i]);
        });
    });

    // Function to generate normalized Fibonacci sequence with variation
    ~generateNormalizedFibonacci = { |length|
        var fib = [0, 1];
        var maxVal;

        (length - 2).do {
            fib = fib.add(fib[fib.size - 1] + fib[fib.size - 2]);
        };

        maxVal = fib.maxItem;
        fib = fib.normalize(0, 1);
        fib = fib.collect { |val| (val * 0.8) + (0.2.rand) }; // Add variation
        fib[0] = 0; // Ensure the first element stays 0
        fib;
    };

    // Function to generate Euclidean rhythm with continuous values
    ~generateEuclideanRhythm = { |steps, pulses|
        var pattern = Array.fill(steps, 0);
        var bucket = 0;
        steps.do { |i|
            bucket = bucket + pulses;
            if (bucket >= steps) {
                bucket = bucket - steps;
                pattern[i] = 1.0.rand; // Use random value between 0 and 1
            };
        };
        pattern;
    };

    // Function to generate beat patterns
    ~generateBeatPattern = { |trackIndex, pattern|
        var newPattern;
        switch(pattern,
            \house, { newPattern = [1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0].collect { |v| if(v == 1) { 0.7 + 0.3.rand } { 0 } } },
            \alternating, { newPattern = [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0].collect { |v| if(v == 1) { 0.7 + 0.3.rand } { 0 } } },
            \hihat, { newPattern = { 0.7 + 0.3.rand }.dup(numSteps) },
            \random, { newPattern = { 1.0.rand }.dup(numSteps) },
            \fibonacci, { newPattern = ~generateNormalizedFibonacci.(numSteps) },
            \euclidean, {
                var pulses = (1..numSteps-1).choose;
                newPattern = ~generateEuclideanRhythm.(numSteps, pulses);
            }
        );
        patterns[trackIndex] = newPattern;
        sliders[trackIndex].value_(newPattern);
    };

    // Create beat pattern buttons
    [
        ["House", \house],
        ["Alter", \alternating],
        ["Hi-hat", \hihat],
        ["Random", \random],
        ["Fib", \fibonacci],
        ["Eucl", \euclidean]
    ].do({ |data, index|
        numTracks.collect({ |trackIndex|
            Button(window, Rect(300 + (index * 45), 10 + (trackIndex * 100), 40, 20))
            .states_([[data[0]]])
            .action_({
                ~generateBeatPattern.(trackIndex, data[1]);
            });
        });
    });

    // Function to print all patterns
    ~printAllPatterns = {
        var code = "((\n";
        numTracks.do({ |i|
            code = code ++ "    Pdef(\\track" ++ i ++ ",\n";
            code = code ++ "        Pbind(*[\n";
            code = code ++ "            type: \\cln,\n";
            code = code ++ "            snd: \\mmd,\n";
            code = code ++ "            num: " ++ (i % 4) ++ ",\n";
            code = code ++ "            dur: 0.15,\n";
            code = code ++ "            rel: 0.9,\n";
            code = code ++ "            sustain: 1,\n";
            code = code ++ "            amp: Pseq(" ++ patterns[i] ++ ", inf),\n";
            code = code ++ "            type: Pif(Pkey(\\amp) == 0, \\rest, \\cln),\n";
            code = code ++ "        ])\n";
            code = code ++ "    ).play(quant: 2.4);\n";
        });
        code = code ++ ").play;\n)";
        code.postln;
    };

    // Add a "Print All Patterns" button
    Button(window, Rect(10, 420, 150, 30))
    .states_([["Print All Patterns"]])
    .action_({ ~printAllPatterns.() });

    // Add Play/Stop button
    Button(window, Rect(170, 420, 80, 30))
    .states_([["Play"], ["Stop"]])
    .action_({ |btn|
        if(btn.value == 1) {
            playing = true;
            currentStep = 0;
            Routine({
                while { playing } {
                    {
                        stepIndicators.do({ |indicator, i|
                            indicator.string_("Current step: " ++ currentStep);
                        });
                    }.defer;
                    pdefs.do(_.play(quant: 0.15));
                    0.15.wait;
                    currentStep = (currentStep + 1) % numSteps;
                }
            }).play(TempoClock.default);
            "Started playing".postln;
        } {
            playing = false;
            pdefs.do(_.stop);
            "Stopped playing".postln;
        };
    });

    // Initialize patterns
    numTracks.do({ |i| ~updatePattern.(i) });
    "Sequencer GUI created".postln;
};
)

I’m willing to help further, but I’m not free at the moment to do so (SC forum time will be limited for the next several days). Can you be patient?

hjh

1 Like

Absolutely. I apologize. I have no life. Im sorry

Without digging deep into this question at the moment – there can be a difference between internal data structure and external interface. It might be convenient for playback to represent 64th notes using 64-element arrays (or it might not), while the interface presents something else to the user.

I don’t know how the sp1200’s interface handled the entry of 64th-notes. I imagine it was somehow different from a big array of 64 buttons/faders – perhaps by allowing a 16th-note step to be expanded, and show 4 notes inside the 16th-note…? I’m not very good at GUI design so I won’t try. But this would be one way that the user interface could differ from the underlying data.

If this is a “genius level of programming,” remember that you get there step by step. Requirements that are too complex for now, you can table them and work on something simpler, and learn from that. The ideal is when your project is just outside of what you can do comfortably, but not too far outside: same as learning an instrument, or learning DAW production, or learning mixing. In all cases, you might want to “skip to the good stuff” but a more incremental learning process gives you a more solid understanding of the fundamentals.

hjh

sound advice. I agree, Thank you. I could get a lot done just with the basic pattern library, but sc is so exciting with what is to offer. Bt yeah, I learned guitar the same way. One thing at a time. Master something, then moved on.