[ ]:
# header / imports
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
import sc3nb as scn
[ ]:
from sc3nb import Buffer

example_file = "../media/blip.wav"
[ ]:
sc = scn.startup()

Buffer

Buffer is the Python class in sc3nb to interface with Buffers on the SuperCollider server.

[ ]:
# uncomment following line to see help for the Buffer class:
# help(scn.Buffer)

Create Buffer from a numpy.Array

[ ]:
d0 = np.random.rand(30000, 1)
[ ]:
buf0 = Buffer().load_data(d0)
buf0

In this case a default buffer with default sample rate (44100) and default insert mode is created.

If you want to create a buffer with a specific sample rate or OSC insertion method, etc. look at load_data

[ ]:
scn.Buffer.load_data?

Attention: insertion via OSC is particularly useful for small datasets (e.g. less than 1000 entries). For larger datasets the default ‘file’ mode is much faster.

[ ]:
d0 = np.random.rand(30000, 1)
buf1 = Buffer().load_data(d0, sr=5000, mode='osc')
buf1

Create Buffer with data from PyA Asig

This only works if using pya package: skip if you dont use pya

[ ]:
try:
    from pya import Ugen
except ImportError:
    pass
else:
    a1 = Ugen().sine(440, dur=1.0, sr=2000, channels=2).fade_out(0.5) # 1.0s sine tone of 440 Hz
    a1.plot()
    print(a1)
    buf1 = Buffer().load_asig(a1)
    buf1

Again, default transport method is mode=’file’, i.e. using a temporary file and fill the buffer on sc with this content. * use mode=”osc” to select the direct transfer of data via OSC messages

Create Buffer of .wav File

[ ]:
buf2 = Buffer().read(example_file)
buf2

The buffer method will automatically read the sample reate of the file and set it to Buffer.sr

You can specify further arguments to read

[ ]:
scn.Buffer.read?
[ ]:
buf = Buffer().read(example_file, starting_frame=18000, num_frames=20000, channels=[1])
buf

Allocate an empty Buffer

[ ]:
buf3 = Buffer().alloc(2.5*44100, sr=44100)
buf3

Reuse an existing SC buffer

Buffer.use_existing(bufnum) will force the Buffer to (re-)use a buffer that already exists on the server, identified via its bufnum on the scsynth.

[ ]:
# create a Buffer in SuperCollider
%sc b = Buffer.read(s, Platform.resourceDir +/+ "sounds/a11wlk01.wav");
[ ]:
bufnum = %scg b.bufnum
bufnum
[ ]:
buf4 = Buffer()
buf4
[ ]:
buf4.use_existing(bufnum)
buf4 # bufnum has now changed to be bufnum
[ ]:
buf4.play()

Copy an existing SC buffer

copy_existing allows to copy an already existing buffer into another buffer.

[ ]:
buf5 = Buffer().read(example_file)
buf6 = Buffer().copy_existing(buf5)

This method will automatically use an intern SuperCollider copy method, if both buffer objects use the same sc instance. Otherwise the buffer will be loaded via filesystem. For this to happen, both sc instance should use the same filesystem.

[ ]:
server2 = scn.SCServer(options=scn.ServerOptions(udp_port=57778))
server2.boot(kill_others=False)
[ ]:
sc.server.dump_osc()
[ ]:
server2.dump_osc()
[ ]:
buf7 = Buffer(server=server2).copy_existing(buf6)
[ ]:
buf5sig = buf5.to_array()
buf6sig = buf6.to_array()
buf7sig = buf7.to_array()
fig, axs = plt.subplots(4,1)
axs[0].plot(buf5sig) # signal
axs[1].plot(buf6sig) # copied signal
axs[2].plot(buf7sig) # copied signal on other server
axs[3].plot(buf6sig-buf7sig); # difference (should be 0)
plt.tight_layout()

With this method, the complete buffer with all samples is copied. If you want to copy only a selection of samples, you can use gen_copy() (see below).

Play Buffer

If you want to listen to the buffer, you can use play.

[ ]:
d = np.sin(2 * np.pi * 440 * np.linspace(0, 3, 3 * 44100)**0.9)
buf8 = Buffer().load_data(d)
[ ]:
playbuf_synth = buf8.play()
playbuf_synth

As you can see play() returns an sc3nb Synth object for the Buffer.

This allows to control the playback via the synth class while the synth is running.

[ ]:
playbuf_synth.rate = 0.5
[ ]:
if not playbuf_synth.freed: # stop the playback if not done already
    playbuf_synth.free()
    playbuf_synth.wait()
[ ]:
playbuf_synth = buf8.play(rate=10, amp=0.15, pan=1)  # play at given rate and pan
[ ]:
playbuf_synth.wait(timeout=6)  # wait for synth to finish

You can get a description of the possible arguments with

[ ]:
scn.SynthDef.get_description(playbuf_synth.name)

and even can see the SynthDef here:

[ ]:
buf8._synth_def

You can get a description of the possible arguments with

[ ]:
scn.SynthDef.get_description(playbuf_synth.name)

As you can see the SC synth will free itself when done if you are not using the loop argument.

However with loop enabled you need to free the synth manually.

[ ]:
synth = buf8.play(rate=-4, loop=True)  # play looped
[ ]:
synth.rate = 1 # change controls as needed
[ ]:
synth.free()

For more information regarding the Synth class, please refer to the Node guide.

Write Buffer content to file

Write the content of a buffer into a file. By default it is a .wav File with float as sample. You can change it via parameters “header” and “sample”.

[ ]:
buf9 = Buffer().load_data(np.random.rand(10000)-0.5)
[ ]:
buf9.write("../media/output.wav")
[ ]:
# !ls -la ../media # uncomment if your shell offers ls

Fetch Buffer content to array

[ ]:
# create a buffer
buf2 = Buffer().read(example_file)
[ ]:
data = buf2.to_array()
[ ]:
plt.plot(data);
[ ]:
buf2.play(rate=1)

Fill Buffer with values

Fill a Buffer with zeros:

[ ]:
scn.Buffer.zero?
[ ]:
buf = Buffer().alloc(100)
buf.zero()
plt.plot(buf.to_array());

Fill a Buffer range with values:

[ ]:
scn.Buffer.fill?
[ ]:
buf = Buffer().alloc(500).fill(0, 90, 22).fill(200, 100, 5)
plt.plot(buf.to_array());

Alternatively: fill buffer with single fill statement using multiple value triplets

[ ]:
buf.fill([20, 50, -8000, 200, 100, 8000])
plt.plot(buf.to_array());

Fill Buffer with sine wave harmonics of given amplitudes.

[ ]:
scn.Buffer.gen_sine1?
[ ]:
buf = Buffer().alloc(500).gen_sine1([1,-0.5,0,1.4,0,0,0.2])
plt.plot(buf.to_array());

Fill Buffer with sine wave partials using specified frequencies and amplitudes.

[ ]:
scn.Buffer.gen_sine2?
[ ]:
buf = Buffer().alloc(1024).gen_sine2([[3.1, 1], [0.2, -2.5], [30, 0.3]])
plt.plot(buf.to_array());

Fill Buffer with sinus waves and given frequency, amplitude, phase

[ ]:
scn.Buffer.gen_sine3?
[ ]:
buf = Buffer().alloc(1024).gen_sine3(
    [[1, 0.9, 1], [2, 0.3, +np.pi/2], [3, 0.3, 3]])
plt.plot(buf.to_array());

Fill Buffer with series of chebyshev polynomials:

[ ]:
scn.Buffer.gen_cheby?

\(\textrm{cheby}(n) = \textrm{amplitude} \cdot \cos(n \cdot \arccos(x))\)

[ ]:
buf = Buffer().alloc(1024)
ch = [1]
for i in range(4):
    ch.insert(0, 0)
    buf.gen_cheby(ch)
    plt.plot(buf.to_array(), label=str(i));
plt.legend();

gen_sine1 to gen_sine3 and gen_cheby have the optional parameters: * normalize: Normalize peak amplitude of wave to 1.0. * wavetable: If set, then the buffer is written in wavetable format so that it can be read by interpolating oscillators. * clear: if set then the buffer is cleared before new partials are written into it. Otherwise the new partials are summed with the existing contents of the buffer.

Copy data of another Buffer:

[ ]:
scn.Buffer.gen_copy?
[ ]:
buf1 = Buffer().alloc(1024).fill(1024, 0, 0)
plt.plot(buf1.to_array());
buf2 = Buffer().alloc(1024).gen_sine1([1,0.5,0,1.4,0,0.5,0.2])

# copy samples 0..0+400 of buf2 into buf1 at position 2++
buf1.gen_copy(buf2, 0, 2, 400)
plt.plot(buf1.to_array());

# copy samples 250..end(=<0) of buf2 into buf1 at position 250++
buf1.gen_copy(buf2, 0, 250, 400)
plt.plot(buf1.to_array());

Here we copy 100 samples of buf2 at starting pos 1 to buf3 at position 2. Use a negative amount of samples to copy all available samples

Get information about the Buffer

Information about the buffer object:

[ ]:
buf3

Information about the buffer in SC

[ ]:
buf3.query?
[ ]:
buf3.query()

Free Buffers

start with a buffer

[ ]:
buf = Buffer().read(example_file)
buf
[ ]:
buf.query()  # works as intended
[ ]:
buf.free()
[ ]:
buf  # listed as not loaded, python Buffer instance still exists
[ ]:
try:
    buf.query()  # raises an error after buf.free
except RuntimeError:
    pass
else:
    print("Buffer query on freed buffer should raise RuntimeError")
[ ]:
sc.exit()