A Question about Buffers and Float Arrays

Hi there -

I was experimenting writing buffers with arrays.
This will write a stereo buffer, with two independent channels of random values:
~b = Buffer.loadCollection(s, Array.fill(100, {1.0.rand}), 2);

If I wanted to take two discretely generated arrays and combine them into a stereo buffer, though - I’m not sure what the approach would be. Any ideas?

.flop is a lovely way to do this.
Note: to ‘prove’ it works, one channel has positive, and the other has negative sample values

It works with more channels too.

~leftChannel = {1.0.rand.squared}!1000;
~rightChannel = {1.0.rand.neg.cubed}!1000;
~stereoArray = [~leftChannel, ~rightChannel];
~b = Buffer.loadCollection(s, ~stereoArray.flop.flatten, ~stereoArray.size);
~b.plot
1 Like

Also .lace:

~leftChannel = {1.0.rand.squared}!1000;
~rightChannel = {1.0.rand.neg.cubed}!1000;
~stereoArray = [~leftChannel, ~rightChannel];
~b = Buffer.loadCollection(s, ~stereoArray.lace(~leftChannel.size * 2), ~stereoArray.size);
~b.plot

(.lace produces the flat array, so it doesn’t have to be flattened like the .flop result does.)

That is – the .flop.flat way or the .lace way, but don’t do both at once.

hjh

2 Likes

or

~interleaved = [~channel1, ~channel2, ~channel3].flop.reduce('++')
1 Like

OK - that makes sense - when using loadToFloatArray, it seems like the output becomes an interlaced list, with each of the channels.
I was attempting to make a simple function to do buffer operations - but I seem to missing something here:

~reverseBuffer = {|buffer|
	var newa, bucket=[], size = buffer.numChannels;
	buffer.loadToFloatArray(action:{|arr|
	arr.do{|m|bucket = bucket.add(m);};
	});
bucket = bucket.unlace(size);
bucket;
};

This throws an ERROR: Primitive '_ArrayUnlace' failed.

a) Load the array first and then unlace it inside the “action function” where the array is loaded
b) Reverse each channel separately
c) Actually, it does not make a difference. Just forget about unlace; the result of reversing interlaced channels is the same as doing that on each channel separately.

~reverseBuffer = {|buffer|
    var newBuf = Buffer.alloc(s, buffer.numFrames, buffer.numChannels);
    buffer.loadToFloatArray(action: {|arr|
        newBuf.loadCollection(arr.reverse);
    });
    newBuf;
};
~newBuffer = ~reverseBuffer.(~oldBuffer);
1 Like

From some benchmarks I did a while ago, lace is significantly faster!

2 Likes

Thanks for the tip! I will remember if I use it in huge sound files. :blush:

At the same time, it’s good that people know how to compose functions (or methods). (And as a matter of fact, it’s unfortunate that Sclang composes things so poorly regarding performance.)

I agree, but I don’t see how you could fix this without a type system… that is, without extending the ‘bootstrapping’ that the compiler does for things like while, !?, ??, if..., which would mean you can’t overload those methods.

Yea, type systems can enable specific compiler optimizations, but attributing the performance difference to the lack of a type system seems a bit of a stretch. Without profiling data, it would be speculation.

Besides, the new wave of gradually typed languages is sometimes worse than doing nothing.

I was attributing it to the fact you can’t transform .flop.flat -> .lace, as at compile time, you don’t know what those methods do, and even if the method’s content is compile time known, you can’t look them up because you don’t know the type of the receiver.

Supercollider actually does make some assumptions about the body of those above listed methods, it is very confusing when you try to dig into it.

Here is an issue about it showing some of the odd behaviour…

It also enables this magic (from object.sc)

// looping
	while { arg body;
		// compiler magic: the compiler inlines the following loop
		// thus an uninlinable while can be implemented using while itself
		while({ this.value }, {
			body.value
		});
	}
1 Like

Yes, that’s true. I’ve been inspecting the intermediate language that GHC produces to get some performance hints, and it is all about magic things. They do crazy optimizations down there. The intermediate language is also statically typed; knowing what happens after all those layers (there and below) is not trivial. If sclang aimed at type inference, it would be a substantial project. People much more astute than us struggle to get it right.

If I wanted to unpack and apply different equations to different channels in floatArray, though - what would the protocol be?

~leftChannel = {1.0.rand.squared}!1000;
~rightChannel = {1.0.rand.neg.cubed}!1000;
~stereoArray = [~leftChannel, ~rightChannel];
~b = Buffer.loadCollection(s, ~stereoArray.flop.flatten, ~stereoArray.size);
~b.plot

~reverseBuffer = {|buffer|
    var newBuf = Buffer.alloc(s, buffer.numFrames, buffer.numChannels);
    buffer.loadToFloatArray(action: {|arr|
        var channels = arr.unlace(2);
        channels = [channels[0] * 0.1 + 0.1 ,channels[1].reverse * 0.1 - 0.1];
        newBuf.loadCollection(channels.lace);
    });
    newBuf;
};
~newBuffer = ~reverseBuffer.(~b);
~newBuffer.plot

1 Like