Rhythmically skipping through a recording, audio probing

These are arguments to your SynthDef function, or in other words its control inputs. A SynthDef basically associates a unique name (\test in this case, where the \ is what is making it into a unique name, called Symbol in SC) with a function ( all the instructions between {} that follow the name).
The function will be sent to the server (by the .add call you have after the function), because in SC the actual sound synthesis is carried on by a separate program, called the server, or scsynth. There the arguments to the SynthDef function are turned into controls that you can adjust in real-time while the Synth is running.
So, your arguments are out=0, bufNum=0, start=0, end=1000, and this means that you can do things like:

// you can set controls when you create a synth
x = Synth(\test, [bufNum: b, start: 0, end: 29000]);
// and afterwards
x.set(\start, 0);

// for example you can change your buffer
c = Buffer.read(s, "path/to/another/sound/file.wav");
x.set(\bufNum, c);

By the way, it’s not a good idea to overwrite the s variable, because it contains a reference to the Server. Better to change your code to store your synth in another variable, e.g. x.

Yes! Go for it :slight_smile:

EDIT: extra tip about readability: in SuperCollider, try to select a block of code (or all your code) and press Tab. You should get automatic indentation, which makes your code way more readable!

I suppose numSkips isn’t a command or anything like that but a variable that I assigned a value to: So I suppose do has an instruction in it’s arguments (that’s what the things in curly brackets, divided by a comma a called, right?) that defines how many times something is done? One problem here is that I can’t find something in the help files, because searching for do gives me an “Alphabetical index of all methods” and I really don’t know which category is the right one. I guess the more you know the easier things like this are but it’s super hard that you have to know the program in order to fully use it’s helpfiles :slight_smile:

So this variable should be changed to another letter right? I already got some error messages after playing with the code a bit and I thought that it might have to do with the s.erver but I’m rather cautious in not messing with the code to much to change this.

Good to know - again thank you very much for all your efforts, highly appreciated!

Searching the help: put your cursor on a do in your code and press Ctrl (or Cmd if you are on a mac) + d.
You will get a list of all classes that implement the do method. numSkips is exactly a variable, and it contains an Integer number, so when you get to the list scroll down until you find Integer. Also have a look around and see how many classes have the do method!

There is something called the Robinson paradox. Deleuze made the long story short saying that “you can’t make sense of a language before you make sense of all its elements, but you can’t make sense of the elements until you make sense of the whole language”. Terrible, and fascinating, but we do speak, we do learn languages. Keep exposing yourself to it, read other people’s code, write and share your own, make pieces, go out and perform with it (well, this pretty much means stream it at the very present moment).
Also, if you have time, check the Getting Started Tutorial series (you find it in the help browser), @Bruno’s e-book, or @elifieldsteel Youtube Tutorial Series

2 Likes

Aside: I recommend using VDiskIn for hours-long material. I had crashes (at least in the 32-bit version) with just loading hour-long material into (large) buffers. VDiskIn streams the file by loading only a small portion into its buffer.

This would be my next question: Thanks to elgianos help I was able to realize my idea exactly as described. However shrinking an 1 hour long stereo file into a 3 min cutup also introduces some bitreduction-effect. I can’t tell wether it is only downsampling or also bitreduction but i guess it has something to do with

WARNING: The phase argument only offers precision for addressing 2**24 samples (about 6.3 minutes at 44100Hz).

Frankly for the file that I worked on this time based degration fits perfectly but for further use I would like to understand what exactly is happening? Also @RFluff would you mind helping me to insert VDiskIn into the code above? Reading the helpfile I wonder if I simply replace the BufRd object by VDiskIn or is there anything else to mind? I will check by myself tonight/tomorrow when I’m back at my computer but am curious for now…

VDiskIn has no playback position control though… so you won’t have that precision problem, but you also won’t be able to run your process, for which changing playback position is everything. You could try to do super fast, precisely-timed skips though: like, set a playback rate of 1000 for 0.01 seconds to move forward by 10 seconds almost instantly. But there VDisk could give trouble as well, because of a too high playback rate.

I haven’t been working with more than 5 hours of 44.1k (mono) material, but below that I haven’t had problems allocating and reading the buffers. I’m on the 64-bit version though, and 5 hours is more or less everything my RAM can load :slight_smile:

A single precision floating-point number has 24 binary digits of precision, which is multiplied by a power of two to extend the range. 10 is 1.01 x 2^3 (1.01 in binary = 1 1/4, times 8 = 10).

Binary fractions aren’t very intuitive when we’re used to counting in tens, but you can look at the same problem using limited-precision decimal floating-point.

If you have 5 digits of precision, then you can count:

1 = 1.0 * 10^0
2 = 2.0 * 10^0

9 = 9.0 * 10^0
10 = 1.0 * 10^1
11 = 1.1 * 10^1

99999 = 9.9999 * 10^4
100000 = 1.0 * 10^5

… now… what is 100,001?

It doesn’t exist. You need 6 digits of precision to represent this number, but you have only 5.

The next possible number, with five digits of precision, after 100000 is:

100010 = 1.0001 * 10^5

Once you go past the limit of precision, then decimal floating-point starts to count in tens.

That’s what’s happening with the buffer-read phase. Once you go past the 24 bits, then you have access only to every even sample (it’s binary, so it counts in twos).

2 ** 24 = 16777216 samples. 16777216 / 44100 = 380.43573696145 seconds, up to about 6 minutes and 20 seconds.

32-bit processes have a maximum 4GB addressing space. (In non-server versions of Windows, it’s actually 2GB.)

Assuming stereo, 4 GB * 1024 MB/GB * 1024 KB/MB * 1024 B/KB / (44100 samples/sec * 4 bytes/sample * 2 channels) = 4.0 * 1024 * 1024 * 1024 / (44100 * 4 * 2) = 12173.943582766 sec.

12173 / 60 / 60 = 3.38138… hours (but you also have to subtract some memory for the executable code and other data structures)

If you’re on Windows, 2 GB means half that, roughly an hour and a half. If they are very large buffers and scsynth addressing space is fragmented, then there might just not be a big enough block of memory = crash.

hjh

2 Likes

So, what exactly is the correct procedure in order to use longer files?
Interestingly, in SuperCollider the location of the “playhead” and the operation of the read file are intertwined. While every other DAW or software can easily play long files, there is this limit.
So, do you guys split the long files into smaller ones and write a routine where several buffers are used and used one after the other? I guess you could write something use an if statement which doesn’t let go of the synth but tells scsynth to jump to the next buffer / variable?

Sorry one last question: Could you help me understand why the iteration number equals numSkips-1 leads to freeing the server? Ah, it’s more rather the variable that’s assigned to the synth that gets freed, so I gotta change that variable, but still: If my numSkip is set to 500, why does it stop at 499?

because it starts at 0, so the 500th iteration is at number 499.
When i == 500, you would make your start position jump to the end of the buffer (nothing to play there, the buffer is finished).
So it makes sense to free the synth after the 500th iteration has finished, that is when i==499, after the .wait.
By the way, your numSkips.do loop will never reach i==500. The idea is that the do loop executes the function numSkips times, so i will go from 0 to 499. After that, the .do() is finished and it will exit its loop.
So, we could remove the check all together and just free the synth after we exit the loop:

numSkips.do({|i|
    var progress =  i / numSkips;
    var loopStartSample =  progress * b.numFrames;
    var loopEndSample = loopStartSample + 10000;
    // you could set this end value to be precisely the start of next segment:
    // loopEndSample = ( i + 1 ) / numSkips * b.numFrames;
    s.set(\start, loopStartSample, \end, loopEndSample);

    durationWait.wait;
});
// after the loop exits, code will continue to be executed from here
s.free;

tangential recommendation:
don’t use the s variable.

It’s traditionally used to refer to the server, and using it for something else, as has happened here, often leads to confusion (as it has here?).

cheers,
eddi

https://soundcloud.com/all-n4tural/sets/spotlight

https://alln4tural.bandcamp.com

1 Like

I wish I knew as well! I have three options as of now:

  1. Accept degradation of files which are longer than about 6 minutes (or less if they are played at very slow rate). This has been my main :slight_smile:
  2. Split long buffers in shorter buffers
  3. I’m trying to contribute to super-bufrd because I would really like to have a simpler way with no degradation. It looks very very promising IMO

This topic is important to me… but maybe is it a bit OT here? Maybe let’s move it to the other thread :slight_smile:

sigh
Would you give an example of a routine where several 6 min. buffers are sequentially used…? Each buffer get’s it’s own variable and after freeing the buffer, the same sequence is repeated for the following buffer?

Actually I forgot to mention one important thing: the “precision loss” problem happens in this case when we communicate the phase between Phasor and BufRd.
You could use PlayBuf to minimize the problem, since it keeps its phase internally!
So try rewriting your SynthDef using PlayBuf instead of BufRd and Phasor. It would be the simplest workaround

If you need to have start and end point (a loop) then take a look at LoopBuf from sc3-plugins

1 Like

Do you mean, playing the long file continuously (but split into buffers)? If so, elgiano is right that PlayBuf and maintain accuracy for longer (because its internal phase is double precision = 54 bits rather than 24).

Asking because the thread started with the idea of random access (“rhythmically skipping”). That would be a different solution.

hjh

Hey; I imagined a long file being cut into smaller files before moving to SuperCollider.
It takes me some longer to figure stuff out, still working on implementing PlayBuf…

By rhythmically skipping I meant a fixed rhythm of a given rate. Not a random access (agogic skipping?)

Check PlayBuf’s Help File. In case you don’t know it, you can open an Help page (Documentation) for every class in SuperCollider by putting your cursor on it and pressing Ctrl+D (or Cmd+D on a mac).
It explains all the arguments PlayBuf takes and also gives some code examples you can execute right away :slight_smile:

The only catch about PlayBuf compared to BufRd is that when you change its start position you need to retrigger it. It won’t jump to the new position by itself. In your case you can do it easily like this: automatically trigger PlayBuf when the start position changes.

SynthDef(\player){|out=0, bufNum=0, start=0|
    Out.ar(out,
        PlayBuf.ar(1, bufNum, 
            rate: BufRateScale.ir(bufNum), 
            trigger: Changed.kr(start), 
            start: start, 
            loop:0
        )
    )
}.add;

x = Synth(\player,[bufnum:b]);
// now when you set a new start, PlayBuf will jump to it (popping)
x.set(\start, 44100);
x.set(\start, 44100 * 2);
x.set(\start, 0);
// ...

And PlayBuf doesn’t take an end argument. We are not anymore controlling a “playhead” directly, there is no Phasor (since the precise phase is handled internally by PlayBuf), but we can still make it jump to a new position by changing the start parameter and sending a trigger to its ‘trigger’ parameter (which updates the internal phase to the new start).
So, you won’t get those small loops anymore. Instead, the player will just continue playing. (Want the loops again? One way with PlayBuf is to “retrigger” it rhytmically with an Impulse.kr(1/loopDur): trig: Changed.kr(start) + Impulse.kr(1/loopDur) )

The reason why I’m not sending you yet some code for loading a soundfile into separate shorter buffer is that loading them is quite straightforward, but then we would have to find a clever system to handle the situation when a “skip” happens across the boundaries between two different buffers. I can’t find a way to do it without adding a lot of complexity to your code, which can be avoided by using PlayBuf with only one “long” buffer.

I would avoid that by loading buffers per played segment, rather than a fixed arrangement. Can’t post an example now – maybe tomorrow.

hjh

Here’s a way to do “rhythmically skipping” through a file, without a large buffer and without phase precision issues.

At any time, you need three buffers:

  • One of them is playing.
  • Another may have just released (still fading out) – so it needs to remain stable.
  • So the third one can be reading from disk.

These should be allocated with the longest duration of a segment you plan to play – not the entire sound file.

The Prout is the key. It “rotates” the buffers into their new roles for the next event to play, loads a segment for the following event, and then yields the buffer that should play this time.

Buffer.read (at least in 3.11) passes the starting frame as an integer, so you get at least 31-bit precision (I forget if it’s treated as signed or unsigned – if signed, then you lose the sign bit). That’s the 6.xyz minutes we were talking about before * (2 ** 7) = over 768 minutes = over 12 hours, that you can access with sample accuracy.

This example, again, isn’t playing the sound file continuously. For that, I think elgiano’s suggestion is easiest (PlayBuf). (The thing that would be tricky about playing segmented buffers continuously is that language-server communication is not sample accurate. So the “hand-off” from one buffer to the next would have to be triggered within SynthDefs – it wouldn’t be completely smooth to do it with a pattern. It’s certainly possible, but a bit more than I have bandwidth to try right now.)

EDIT: This example is doing random access. To repeat the same segment rhythmically, it’s easier: no need to load a buffer on every iteration.

hjh

~path = "/media/dlm/7D53-8744/Docs/ardour/19-0808-quietcode-01/19-0808-quietcode-01-edit.wav";
f = SoundFile.openRead(~path);
~frames = f.numFrames;
~channels = f.numChannels;
f.close;

s.boot;

b = Array.fill(3, { Buffer.alloc(s, 88200, ~channels) });

(
SynthDef(\segment, { |out, bufnum, rate = 1, time = 1, amp = 0.1|
	var eg = EnvGen.kr(Env.linen(0.01, time, 0.01), doneAction: 2),
	sig = PlayBuf.ar(~channels, bufnum,
		rate * BufRateScale.kr(bufnum));
	Out.ar(out, sig * (eg * amp));
}).add;
)

(
p = Pbind(
	\instrument, \segment,
	\startFrame, Pwhite(0, ~frames - 100000, inf),
	\bufnum, Prout { |ev|
		var playing, releasing, loading, temp;
		#playing, releasing, loading = b;
		
		// first time through the loop, haven't loaded anything
		// load something, and rest (because 'playing' isn't ready yet)
		loading.read(~path, ev[\startFrame]);
		ev = Rest(playing).yield;
		
		loop {
			// playing is now the *previous* buffer that was playing
			// playing --> releasing etc.
			temp = playing;
			playing = loading;  // was loading, should play next
			loading = releasing;  // released last time, should load next
			releasing = temp;
			
			loading.read(~path, ev[\startFrame]);  // to be ready next time
			
			ev = playing.yield;  // use this buffer as 'bufnum'
		}
	},
	\dur, Pwhite(1, 4, inf) * 0.25,  // rhythmically
	\time, Pfunc { |ev| ev[\dur] / thisThread.clock.tempo },
	\amp, 1
).trace.play;
)

p.stop;
1 Like