Controlling Buffer Sequence

Hi,

I’m doing a project communicating SC and Arduino.
It’s an interactive narrative (composed by audio files) that is controled by the Arduino.

I made an array of audio buffers and I want to control the order that they will play according to a digital input.

(
s.latency= 0.05;
b.do{|x| x.free};	//free all previous buffers
b= [	//edit paths - any number of files - make sure mono
	Buffer.read(s,"D:/Users/Daniela Tinoco/Music/domenico_mini.wav"),
	Buffer.read(s,"D:/Users/Daniela Tinoco/Music/bixiga_mini.wav"),
	Buffer.read(s,"D:/Users/Daniela Tinoco/Music/curumin_mini.wav"),
	Buffer.read(s,"D:/Users/Daniela Tinoco/Music/tincoas_mini.wav"),
	Buffer.read(s,"D:/Users/Daniela Tinoco/Music/russo_mini.wav")
];
SynthDef(\sample, {|out= 0, buf, rate= 1|
	Out.ar(out, PlayBuf.ar(1, buf, rate, doneAction:2)!2);
}).add;
)

//test - looping through samples one after the other
(
Routine({
	var index= 0;
	var c= Condition.new;
	inf.do{
		var a= Synth(\sample, [\buf, b[index], \rate, 1]).register;
		a.onFree{
			("playing index:"+index).postln;
			c.unhang;
		};

		c.hang;
		index= index+1%b.size;

	};
}).play;
)

How can I control the index value by the Osc msg value?

Hi Daniela, and welcome!
I’d like to help you but this is absolutely out of my reach :frowning:

To make sure that we solve the right problem, I think we need to make the requirements a bit more specific first: will you

  1. send one OSC message that contains a complete sequence of indices to be played, or
  2. send successive OSC messages each containing a “next” index or list of indices to be played

In case of scenario 2, as soon as a new OSC message comes in, would you like to

  1. stop whatever is playing and start the newly received index immediately, or
  2. queue the received index and play it when it’s its turn to play (let the one that’s already playing finish first and the already scheduled ones play first), or
  3. let the currently playing buffer finish first, then start the most recently requested buffer playing, while forgetting about all the requests that came between starting the currently playing one and the most recently received one, or
  4. start to play the received buffer index immediately in parallel with other buffers already playing

(or something else still?)

Hi,

I’m sending successive OSC messages.
I would like to let the currently playing buffer finish first, then start the most recently requested buffer playing, while forgetting about all the requests that came between starting the currently playing one and the most recently received one.

I have a flowcharch of audios.
Ex.: The first audio will play, then, there will be two options. The second audio will be choosen according to the input value, and it will play immediately after the first one.

Is it more clear now?

Thanks for the help

Here’s one possibility, although perhaps more efficient ones can be thought of.
I probably should use a mutex to protect ~next_index

// this program block installs an OSC def that listens to OSC messages and triggers playing a buffer based on the arguments
(
s.waitForBoot({
    // variable to remember the most recently requested index
    ~next_index = nil;
    // flag to decide if new synth can start
    ~prevent_next = false;
    
    //free all previous buffers
    b.do{|x|
        x.free
    };
    
    // load some buffers
    b= [ //edit paths - any number of files - make sure mono
        Buffer.read(s,"/home/development/supercollider/playindex/wavs/1.wav"),
        Buffer.read(s,"/home/development/supercollider/playindex/wavs/2.wav"),
        Buffer.read(s,"/home/development/supercollider/playindex/wavs/3.wav"),
        Buffer.read(s,"/home/development/supercollider/playindex/wavs/4.wav"),
        Buffer.read(s,"/home/development/supercollider/playindex/wavs/5.wav")
    ];
    
    SynthDef(\sample, {|out= 0, buf, rate= 1|
        Out.ar(out, PlayBuf.ar(1, buf, rate, doneAction:2)!2);
    }).add;
    
    s.sync;
    
    // install an osc handler reacting to the playindex message; when an osc msg comes in, register the argumetn in ~next_index
    n = NetAddr("127.0.0.1", 57120); // listen to messages on the local machine
    OSCdef(\playindex, {
        |msg, time, addr, recvPort|
        ~next_index = msg[1].asInteger;
        ~next_index.debug("Next index requested to play");
    }, '/playindex', n); // def style
    
    
    // in a thread, check if a new index is ready to be played
    fork {
        "Waiting for indices...".postln;
        while ({true}) {
            if (~next_index.notNil && ~prevent_next.not) {
                var synth;
                var index = ~next_index;
                ~prevent_next = true;
                ~next_index = nil;
                index.debug("Start playing index");
                synth = Synth(\sample, [\buf, b[index]]);
                synth.onFree({
                    ~prevent_next = false;
                    "Ready for new index to be played.".postln;
                });
            };
            0.25.wait; // check every 0.25 seconds for a new buffer
        };
    };
});
)

// with the previous program running, we can send some messages to it to see if it works
// instead of sending things from inside supercollider, these could come from outside supercollider
(
m = NetAddr("127.0.0.1", 57120); // loopback
fork{
    m.sendMsg("/playindex", 1);
    1.wait;
    m.sendMsg("/playindex", 4); // check that this one is overwritten by the next one
    m.sendMsg("/playindex", 2);
    5.wait;
    m.sendMsg("/playindex", 0);
    10.wait;
    m.sendMsg("/playindex", 3);
};
)
1 Like

Thank you so much!

Now I have 2 other problems to solve:

  1. All the buffers can be played in any time of the experience. The idea is to release some options according to the buffer played at the time;

  2. I’m sending another OSC message that will be updating the audio rate. Writing the “synth.set” inside the “if” will only change the rate once, when the audio starts. How can I update it constantly?

Again: thank you so much for the help

  1. should be not too difficult. You can free the buffers when they are done playing. It requires that you remember the currently playing buffer (remember that the playindex can be overwritten by incoming OSC msgs at any time).

    So, at the moment you start to play a buffer, copy its index to a ~variable, and during the onFree of the synth you can free the buffer contained in ~variable. Of course now you have to ensure that no one starts that buffer again. Either trust that the incoming msgs have been checked properly or register the freed buffer indices in some list of forbidden indices and when checking if a buffer should start, check that the requested index is not in that list.

    Note that for installations that run continuously, maybe it’s better to keep all buffers loaded otherwise someone needs to restart your program. The drawback of course is memory usage if very many or very large buffers are used.

    An alternative (perhaps better for your use case?) approach conserving memory space might be to just load the buffer you’re about the play when it it’s supposed to start playing, and free it always when it’s done. Of course it will take a little time to load the buffer just before playing, but my guess is it may not really be a problem (depends a bit on size of the buffers and the power of the computer, speed of the hard disk etc). You can use s.sync to ensure the buffer is fully loaded before calling buffer.play.

  2. Make the buffer playback rate an argument of the synth, then you can use synth.set to update it while it’s playing.

    Instead of making synth a local variable inside the if, you can make it a variable with a tilt (= environment variable, almost a global variable) ~synth so it can be accessed from outside the if as well, e.g. in an OSC message handler that updates its rate.

1 Like