Search code examples
supercollider

Looping though a Buffer Array in Supercollider


I am trying to loop through an array of Buffers each containing a sound sample read from disk, but I am having problems getting the SynthDef to reset its pointer to the buffers.

I did the following:

  1. Assume I have a folder of sound files and I have read them all into an array of Buffers called "~buffers"

  2. I just want to go through the array in order, playing the samples back to back and stopping after the last one.

  3. I define a simple SynthDef, and then put the Synth that calls it into a Routine:

    (
     SynthDef(\playBuffer,{arg out = 0, buf;
             var sig;
             sig = PlayBuf.ar(2, buf, doneAction: Done.freeSelf); 
             Out.ar(out, sig);
             }).add
    
     ~routine = Routine({
                ~buffers.do({
                    arg item;
                    var synth;
                    synth = Synth(\playBuffer, [\buf, item]);
                    item.duration.wait;
                    synth.free;
                 });
               });
    ~routine.play;   
    )    
    

It does not work as expected---the synths are always playing the same sound,the first one, although for the durations corresponding to the different samples.

I think the problem could be that the function inside my \playbuffer SynthDef (at least according to the Help files) is not re-evaluated with a different bufnum argument inside the loop.

In fact I can loop through the buffers if I use Buffer.play which creates synthDef's and Synth's on the fly. Replacing my Routine with this code works:

       (
        ~routine2 = Routine({
                     ~buffers.do({
                      arg item;
                      item.play;
                      item.duration.wait;
                    });
                 });
         ~routine2.play;
        )

BUT: it is very crude, as now I cannot manipulate the buffer output except for changing the amplitude through the mul parameter of Buffer.play. What I would like to do is to replicate Buffer.play's behavior---creating SynthDef's and Synth's on the fly---in my own code. But I'm having no luck with it. In fact I am not sure where to start, possibly because I don't fully grasp SuperCollider's server's handling of functions. Should I make a Synth-making function and use that inside the routine's loop? Or should I move the definition of the SynthDef inside the loop (which seems equivalent)? I tried the latter, but still got the same sound playing.

Perhaps I am going at this the wrong way---I'm very new to SuperCollider.


Solution

  • The code in your first example is correct. If I fill by buffers like this:

    (
    s.makeBundle(nil, {
        ~buffers = [1, 2, 3, 4, 5].collect {
            |i|
            var b;
            b = Buffer.alloc(s, 44100, 1);
            b.sine3([100, 150, 175] * i, 0.25);
        };
    })
    )
    

    and then play them with your code example:

    (
    SynthDef(\playBuffer,{arg out = 0, buf;
        var sig;
        sig = PlayBuf.ar(1, buf, doneAction: Done.freeSelf); 
        Out.ar(out, sig);
    }).add;
    
    ~routine = Routine({
        ~buffers.do({
            arg item;
            Synth(\playBuffer, [\buf, item]);
            item.duration.wait;
        });
    });
    ~routine.play;   
    ) 
    

    This works fine, I hear ascending tones. (I changed your example to be single channel buffers, and removed the .free as you were already doing Done.freeSelf). If you're hearing the same sound playing each time, the problem is likely in the code where you're loading your buffers and not in playback.

    One gotcha: the duration property of a buffer is not available immediately after you load them - reading an audio file is asynchronous, and SC doesn't know the duration until it's loaded. If you're doing Buffer.read immediately before playing, there's a chance your duration might be e.g. 0 or nil, which would cause unexpected results.