The previous page described a technique to detect attacks in an
audio signal. I use this technique for the instantaneous slicing of an
audio stream from the soundcard. While the audio is refreshed, the
newest slices are immediately and automatically available for playback,
with speed and direction of choice. With a sequencer, the sliced
acoustic notes are transformed to rhythm section on the fly. It is
quite rewarding to hear your sounds turn into music so fast. On this
page, I want to illustrate some practical details of the concept.
[slicerec~]
The audio is recorded into a small circular buffer with the
[slicerec~] object for Pure Data. I found that a buffer of about 6
seconds is sufficient for my purpose, though it could be longer if
required. Here you see [slicerec~] with it's connections:
![]() |
The buffer in which [slicerec~] writes has length
'power-of-two-plus-one-sample'. The power of two is for efficient
operation like bitmasking. The one sample extra is for linear
interpolation, and it holds the same value as the sample at index 0.
The [slicerec~] object is constantly analysing the input signal to
find attacks or identify silence. When an attack is detected, recording
starts or continues, and a cuepoint indicating the attack start
location is issued from the left outlet. When the signal decreases
below a user-definable level, recording is paused and a cuepoint of
that location is issued from the middle outlet. When a new attack is
found, recording resumes. It's cuepoint is identical to the silence
cuepoint, there is nothing inbetween. There is only useful audio in the
buffer.
[slicerec~] can receive messages to alter it's analysis parameter
settings. Details of the analysis are on the previous page. Although these settings
can be tuned to input type and conditions of performance, they do not
require a lot of attention, and for many cases the object defaults are
valid.
![]() |
Slicing a circular buffer, where recording goes on continuously, is not
so straightforward. There is always one slice which wraps around the
buffer end, and there is always one slice being overwritten by a new
one. Moreover, the amount of slices in the buffer is not fixed, because
it depends on their length. Therefore, apart from a stale slice, you
can also have a bunch of stale cuepoints. Here is an illustration with
a wrapped- and a stale slice:
![]() |
By far the simplest way of avoiding stale cuepoints is to oversize
the audio buffer, and use a fixed amount of cuepoints, the most recent
ones everytime. Playing a wrapped-around slice is no problem, although
it requires some adjustments which will be discussed further down.
By definition, the start point of one slice terminates the preceding
slice:
![]() |
Still it is useful to store both start and end point for slices,
because the end point of the most recent slice is not the start point
of the oldest slice in the records. So it is best to wait for the end
of a slice, and then store start and end point together. The end can be
induced by silence or by a new attack, and in both cases it comes in
due time. A table with cuepoints can have this structure:
![]() |
This table can also be handled as a circular buffer, where the
newest pair of cuepoints overwrites the oldest pair in the table.
Provided
the audio buffer is large enough to hold the slices, you end up with a
table which has fresh legitimate
cuelists only, all of which can be referenced for playback,
[sliceplay~]
Now comes the question of how to read data from the audio buffer at
different speeds, forward and backward, and eventually across the
wrap-around point. To start with, the number of samples in the slice
must be calculated. In most cases, subtracting the start index value
from the end index value would simply give the answer. But for a
wrapped-around slice, the start index is larger than the end index, and
the result would be a negative number. Eating the sign of that result
will not lead to the correct number of samples. Let me do a hypothetic
example to illustrate how the number of samples can be calculated, no
matter where the start and end point are:
![]() |
The loop in the sketch above has length 8, and it's indexes run
conventionally from 0 till 7. The hypothetic slice has three samples at
index 7, 0 and 1. The start point is at 7, and the end point is at
index 2, because that is the start point of the next slice. The
computation method goes like this:
length = (end point - start point + loop length) modulo loop length
the example: (2 - 7 + 8) modulo 8 = (-5 + 8) modulo 8 = 3 modulo 8 =
3
Three samples, this is correct, but what is the modulo good for? It
does not seem to make any difference here. But look at the case where
start- and end points are swapped respective to the previous case:
![]() |
again: length = (end point - start point + loop length) modulo loop
length
the example: (7 - 2 + 8) modulo 8 = (5 + 8) modulo 8 = 13 modulo 8 =
5
The result is 5 samples indeed. One calculation method applies for
both cases. That is convenient. For loop lengths which are a power of
two, modulo computation can be done by bitwise-and masking, that is
extra convenient.
Now we know the number of samples which should be played, if at the
original speed. At different speeds, the playback time is computed with:
playback time in number of samples = original number of samples /
abs(speed)
The playback time is calculated by [sliceplay~] from the desired
playback speed given in it's left inlet and the start/end points given
as a pair in the right inlet. A countdown timer is set for the playback
time and playback will stop when the countdown reaches zero.
![]() |
During playback, another counter runs upward from zero, and the
playback pointer index is calculated by multiplying this upward counter
with the playback speed. The playback speed is obviously a floating
point number. For reasons of precision, I do not use floating point
increments, but the multiplication instead. The result must be cast
back to an integer index. The difference between floating point value
and it's rounded value is computed (typecasting again), and used as a
fraction for linear interpolation. This relieves aliasing effects. With
speeds faster than 1 or negative speeds, the indexes could run beyond
the buffer bound. This is avoided by applying exactly the same trick as
used before: adding the loop length and wrapping the result modulo loop
length. For negative speeds, playback starts at the end point minus one
sample. Well, these things are all handled inside the object.
instantaneous slicing
Although [slicerec~] and [sliceplay~] are specialised for realtime
audio processing, their use is not preconceived otherwise. Because
[slicerec~] records into a named buffer, this buffer can be accessed by
other objects, and it can be graphed for test or illustration purposes.
More than one [sliceplay~] object can read from the same buffer
simultaneously, reading different slices at different speeds. This is
how I assemble a complete gamelan orchestra from the sounds of my tiny
kalimba.
Each [sliceplay~] instance is monophonic, and it will play it's
assigned slice from start till end uninterrupted, immediately after
cuepoints are given. Cuepoints arriving at the inlet during playback
are simply discarded. No windowing is done at playback, and when
complete slices as recorded and cue'd by [slicerec~] are played, this
should not be necessary anyway. If you want
to play back incomplete slices, it is of course possible by altering
cuepoints accordingly. You will need to organise fade-in and/or
fade-out externally, to eliminate the ugly clicks which arise from
arbitrary cuts.
The Pure Data abstraction slicerec~-help introduces the objects and
their possible configuration. There is one recorder, four players, and
a simple sequencer in a subpatch, triggering the players.
![]() |
The fact that slices are played uninterrupted, means that short slices can be triggered at a higher tempo. It also means that some triggers are ignored, specially with low playback speeds and long slices. A sequence may not play with the exact rhythm which was programmed. On the other hand, this offers an extra opportunity to steer a sequence with the acoustic input, without touching a digital controller. It is an interaction with the machine which I had not anticipated or programmed on purpose.
Around the slice objects, I configured an instantaneous beatslicer
module for Pure Data. It has one recorder, four players
and a sequencer. Below, two such modules are synched in one Pure
Data patch. The left one is set to 'decent' tunings, with playback
speeds being multiples of 0.25, and the one on the right does 'false'
notes. Both have their charm.
![]() |
Still on my wishlist is a method which produces irregular flocks or
swarms of triggers for the slice players. This would extend their use
into a different realm of musical expression.
One more convenient aspect of the [slicerec~] and [sliceplay~]
objects is their CPU friendlyness. The patch above, with two recorders
and eight players in total, is responsable for about one percent CPU
load on my 2GHz MacIntel computer. This implies that instantaneous
beatslicing may be done on quite modest systems as well.
Source code of [slicerec~] and [sliceplay~] can be downloaded from the previous page (bottom). The help file, demonstrating the objects with a sequencer, is included.