[polygrainsynth] is a ready-to-use, OSC-addressable polyphonic
granular synthesizer for Pd.
Its engine is a modified version of
an abstraction by Jamie Bullock.
[polygrainsynth] is the younger sibling of [polywavesynth].
Its implementation of polyphony, using Frank Barknecht's [polypoly] object,
is identical to that of [polywavesynth].
You can hear some music I've made with this synthesizer
here (particularly under "Amber Grains of Wave").
NOTE: if you are a user of pre-version-1 instances of [polygrainsynth], be aware
that there are slight differences between version 1 presets and those of version 0. See
here for more information.
Download the polygrainsynth archive from here.
If you are using Pd-extended of a recent vintage, you should be ready to go. Specifically,
Pd-0.40.3-extended or higher includes Martin Peach's OSC library and the [import] object,
both of which are required.
Download Pd-extended from here --
look for the correct version for your platform.
For use with vanilla Pd, you need to do a few extra things:
extract "mrpeach osc" from Pd-extended and put it somewhere
in your Pd path (such as "extras").
do the same for "cyclone/svf~" (state variable filter).
make an empty "import.pd" object and save it to the "polygrainsynth" folder.
(This will avoid unimportant but annoying error messages.)
[polygrainsynth]'s UI appearance and patch layout work
best with the cross-platform-compatible font used in the latest versions
of Pd-extended -- there may be a little layout awkwardness with vanilla
Pd's font on the various platforms (though it will still be useable).
As mentioned above, [polygrainsynth] is a younger sibling of [polywavesynth];
if you've used [polywavesynth], you'll recognize many of the controls on [polygrainsynth].
In other ways [polygrainsynth] is an entirely different beast, and controlling it takes experimentation
and practice -- granular synthesis can be rather non-intuitive at first. It helps to understand
the architecture, but it's perfectly fine to learn by experimenting, too.
It should be pretty easy to at least get some sound out of [polygrainsynth].
[polygrainsynth_example] has everything set up for you, including an example
preset with an example source-sound preloaded.
Anything in [polygrainsynth] displayed in a "number2" box
(the ones with ">" on their left edge) is an adjustable parameter,
and can be saved and recovered using [sssad].
Normal number boxes are display-only.
(controls identical to those in [polywavesynth]):
the toggle box in the lower left hand corner enables the instance; when it is off,
incoming note-ons are ignored. In a multi-synthesizer setup, this allows quick switch-in,
switch-out of modules.
the 'filter_freq.' slider controls the low-pass cutoff frequency for
the enveloped filter; the 'Q' slider controls filter resonance, with the added
feature that when 'Q' is set to zero, enveloped low-pass filtering is switched
the 'detune' value transposes the input frequency, in cents. This is a very useful
parameter for [polygrainsynth]. For example, some granular effects are best experienced
at extremely low frequencies; a detune of -7200 (six octaves down) is not uncommon!
random per-note panning can be switched on with the "rnd" setting of the
"rnd/fix" radio button. Each new note is given a random position, and the
new voice moves toward that position at a rate in milliseconds given by the
"pan_rate" number box. The "fix" setting corresponds to a "fixed" position,
set by the position slider.
the 'lastKnob' display is ganged up to all the envelope
sliders' outputs, and shows whichever one changed last (a space saving
the 'OSC-UI' toggle box enables live UI response to incoming OSC data.
This provides valuable graphic feedback when using OSC to modulate [polygrainsynth]
parameters, but it can also consume a great deal of available CPU bandwidth
on slower machines.
the 'global' toggle box switches how most UI and OSC controls are sent
to synthesizer voice parameters:
- global-on: parameters affect all voices of a given [polygrainsynth]
module in real-time.
- global-off: parameter changes only affect each newly-attacked voice.
A few [polygrainsynth] parameters cannot be switched:
- (all envelope controls)
The control mode for all other parameters is determined by the setting of
the 'global' toggle box. The 'global-on' setting is especially useful (and fun!) for
responding to quickly-changing OSC data or for live playing of the UI.
The 'global-off' setting allows a more subtle and clean "phase-in" of parameter changes.
The states of the 'global' or 'OSC-UI' toggles are not saved as [sssad] parameters.
granular synthesis controls:
There are five major areas of control over the granular synthesis engine:
sound array -
what is loaded here (and what part of it is selected)
has a great effect on the sound.
pointer control -
this refers to how much and in what direction the pointer jumps
after each grain is played, and whether or not it starts from the same place
in the sound array on each attack.
grain duration - labeled "dur_ms" on
the synthesizer panel, this controls the length of each individual grain
and is given in milliseconds. This parameter affects timbre, and in long
durations (over a couple of hundred milliseconds), the rhythmic nature of the sound.
grain read rate - this is mapped directly to the synthesizer's frequency parameter,
so all frequency controls (freq. input, pitchbend, detune) affect it. Note that
it doesn't always affect the sound in a tonal way, depending on the other granular
grain envelope - currently, the only control over this is a window-steepness value labeled
(for space reasons) envAmp on the synthesizer.
More about sound array control
Any length (mono) sound file can be loaded into [polygrainsynth], of any type loadable by [soundfiler],
which includes wav and aiff. The size of the internal sound array is set by the fourth creation
argument to [polygrainsynth]; any bytes beyond this size will be ignored in loading. To initialize
the sound array, pass a message
containing the full path (or path relative to the [polygrainsynth] abstraction) into
the fourth inlet. After a successful load, you will see the sound displayed in the sound
You may select a subsection of the loaded sound array with the start
and width controls. The best way to use them is to "click-select-roll"
over the number boxes with a mouse; you will see the orange selection area change accordingly.
(Thanks to Steffen Leve Poulsen for this idea.)
These control the "loop" of the pointer. When, in the course of its travels, the pointer
reaches one of these boundaries, it jumps to the other one. See the anchor
control below for another wrinkle on this.
As mentioned above, you can use any length sound file, provided you allocate enough array
space for it with the fourth [polygrainsynth] creation argument. Be aware, however, that
if you wish to switch between sound files live, you'll need to limit yourself to files
of a few seconds or less, to avoid glitches caused by lengthy file operations. A ram disk
can speed up file loads, allowing gltich-less change of larger files. I have found an
array size of 99999 to work cleanly in conjunction with a ram disk for live-switching
Bonus: scribble in the sound display! This comes for free with Pd, i.e., the ability to draw
in array displays. Unfortunately, I don't have a way for you to save the results of this.
More about pointer control
hopsize: this controls the amount (and direction)
the pointer jumps after one grain is played.
It is expressed in milliseconds, may be positive or negative, and assumes a
sample rate of 44.1Khz to calculate the jump size. Use common sense setting
the (hopsize). I.e., if your sound array size is 88,200, that's two seconds
total at 44.1K, so a hopsize of two or more seconds is going to jump right out
of the end of the sound array (and be forced back to the start of the array by
the pointer wrap mechanism) -- not a very interesting effect. On the other hand,
a (hopsize) of +/-0.1 ms., makes a beautiful "phasey" sound.
A (hopsize) of zero gives a very stable sound, basically like a simple sampler.
- anchor: when this control is enabled, the pointer will be forced to
either the start (positive hop size) or end (negative hop size) of the selected subsection
on each and every attack. When disabled,
the pointer wanders around in difficult to predict ways (but still under control of the other
settings; it just doesn't reset to a predictable position on every attack.)
This is very nice for a variable sound, but makes "repeatable sounds" harder to acheive.
Indeterminacy can be injected into pointer hop and grain duration in two
different ways, random variation, or drunken walk within a random variation.
Random variation ("rand" on the panel) controls the maximum positive and
negative deviation from the set value, as a percentage of that value.
When "drunk walk" is non-zero, a drunken walk, with step size set by the "drunk"
number box (as a percentage of the "rand" value), takes place within the bounds
defined by "rand".
(Thus, setting "rand" to zero effectively turns off drunken walking as well, since any percent of zero
The "lag_ms" control for both pointer hop and grain duration simply slews changes to those
parameters, allowing them to change gradually if desired. This is given in milliseconds.
It is particularly useful for grain duration changes; quick changes to "graindur"
will be noisy -- perhaps that's desirable, but if not, use 10 milliseconds or more of lag.
I can't begin to tell you how many permutations of settings will present themselves to
you with this synthesizer. You'll only get a full feeling for this by experimentation.
To help figure out what's going on with [polygrainsynth], a useful tool is [monograinsynth],
included with the distribution. [monograinsynth] can only play one note at a time, and it
doesn't retrigger on legato playing, so it's not even much of a "mono synth". What it can do, however, is
display the waveform pointer in real-time, which is very instructive for understanding how
[polygrainsynth]'s granular engine works. Try the various settings with it, watch the pointer,
Please get creative with the sound source material. Don't just
use the example included; you can "make the instrument your own" with
a little creativity in this area.
N.B.: [polygrainsynth] implements its grain envelope table(s) in a
singleton. Therefore, you can have multiple [polygrainsynth]s open at once,
and only one set of tables will be allocated for all of them, with no
conflict. However, if you close the first-opened patch that contains a
[polygrainsynth], the single instance of the tables will disappear along
with it. This is an unavoidable side-effect of using singletons with a
[closebang]-less Pd. If this happens, just close all patches containing
[polygrainsynth], and re-open -- the singleton will re-establish.
- frequency of note in Hertz; initiates attack or release
- amplitude of note, between 0 and 1 (>0 = attack, 0 = release)
- external "mute" - note-ons are ignored when this is non-zero
- path to sound file
- OSC inlet (see OSC implementation, below)
synth name, used for OSC addressing and preset saving/loading
(allows multiple polygrainsynths to be used simultaneously yet controlled independently)
- number of voices
- voice stealing (0=off; 1=on)
- size of sound array, in bytes
- left: audio left channel
- right: audio right channel
OSC implementation tree:
- /(synth name)
- /controlmode --(value = 0: UI and OSC controls are per-voice; 1: controls are global)
- /enable -------(value = 0: incoming note-ons are ignored; 1: all note-ons honored)
- /gain ---------(value >= 0.0, though be careful with gain > 1.0)
- /pitchbend ----(value in cents)
- /allnotesoff --(any value)
- /freq ---(value in Hertz, triggers or releases note, so should be sent last)
- /amp ----(amplitude multiplier, 0.0-1.0; 0=release >0=attack)
- /ptranchor ----(0 = free-floating pointer; 1 = pointer resets on each attack)
- /detune -------(value in cents)
- /hopsize ------(pointer hop size, value in msecs.)
- /ptrwobble ----(0-100, percent of full hop size, gives a +/- limit for random and
- /ptrdrunkinc --(>0, size of steps for pointer drunk-walk, bounded by the deviation
limits defined by /ptrwobble, above)
- /ptrlag -------(lag time for pointer changes, value in msecs.)
- /graindur -----(value in msecs.)
- /durwobble ----(0-100, +/- percent of full duration, like /ptrwobble, above)
- /durdrunkinc --(>0, size of steps for duration drunk-walk, bounded by the deviation
limits defined by /durwobble)
- /durlag -------(lag time for duration changes, value in msecs.)
- /strtoffset ---(0-100 percent start offset of sound array selection)
- /width --------(0-100 percent width of sound array selection
(n.b.: strtoffset + width should not exceed 100)
- /grainamp -----(0-100, steepness of grain windowing function)
- /env (amplitude envelope, not to be confused with grain envelope)
- /atk ---(value in msecs.)
- /dec ---(value in msecs.)
- /sus ---(0-100, percent of full amplitude)
- /rel ---(value in msecs.)
- /exp ---(envelope curve "exponent" -- see multicurveadsr-help.pd)
- /freq --(value in Hertz)
- /q -----(0=filter switched off -- reasonable values=between 0.1 and 1.0)
- /atk --------(value in msecs.)
- /dec --------(value in msecs.)
- /sus --------(0-100, percent of full amplitude)
- /rel --------(value in msecs.)
- /exp --------(envelope curve "exponent" -- see multicurveadsr-help.pd)
- /fampscale --(0=filter opens fully; 1=scale filter opening to amplitude)
- /position --(0=full left; 1=full right)
- /speed -----(speed of transition to new position, in msecs., >=2)
- /mode ------(0=random; 1=fixed)
The [a_grain2] subpatch at the heart of each voice is very close to Jamie
Bullock's original absraction [a_grain]; it's cleaned up a bit and has a few minor enhancements,
but it works basically the same as the original: audio 'grains' of duration (graindur) are read
from a selection of the source table (grtable) delimited by (strtoffset) and (width),
where offset and width are percentages of total table size. A table lookup for
each grain is performed by a phasor that runs at (readrate) (set by the frequency input).
After each successive
grain has been read, the starting point for the table lookup is incremented by
(ptrhopsize). A windowing function is applied to each grain, and some control
over the steepness of this function is given by (grainamp). Random offsets can
be introduced for the point from which the grain is read (ptrwobble), and for the
grain duration (durwobble). These are expressed as a percentage of the original value.
The random offsets can be random values between -(wobble/2) and +(wobble/2), or change
using a 'random walk' with a step size of (ptrdrunkinc) or (durdrunkinc). Both
pointer movement and grain duration changes can be smoothed by a lag time (in msecs.), which
introduces a linear ramp towards the new value.
The output of the grain engine is run through an
envelope-controlled, variable-frequency and Q-factor low-pass filter. The envelope
opens the filter to the frequency indicated by the "filter_freq." slider,
scaled by the amplitude of the note. This
behavior can be defeated by de-selecting "scale_to_amp" near the filter envelope
controls; in this mode, the filter will open to full range for all notes.
The output of the filter is in turn passed through an amplitude
envelope. Both the filter envelope and amplitude envelope have variable
curves, and ADSR controls.
Finally, the output of the amplitude envelope is split
and panned in one of two ways: either 1) fixed, the l/r balance set by a
number between 0 (full left) and 1 (full right), or 2) a random value
between l and r is chosen just before note attack, with a transition
time set by the pan rate variable.
[polygrainvoice~] tries to be thrifty with CPU cycles. It
uses lookup tables for envelope curves, and each voice switches itself
off when its amplitude envelope completes, so only voices actively
playing consume CPU. Also, the enveloped filter in each [polygrainvoice~]
switches itself off if its 'Q' value is set to zero. So, if you really
need more voices, don't have CPU cycles to burn, and don't mind
giving up per-note enveloped filtering, set 'Q' to zero.
[polygrainvoice~], an individual voice instance for this
synthesizer, is compatible with [polypoly], which is a powerful object
combining the replication features of [nqpoly/nqpoly4] with the
allocation and management capabilities of [poly]. [polypoly] can clone a
compatible object 'n' times, where 'n' is limited only by available CPU
cycles. Using [poly], it automatically allocates voice instances on attack,
tagging them by frequency for de-allocation on subsequent release.
This makes it very easy to instantly create
a polyphonic synthesizer as is done in [polygrainsynth]:
[polypoly $2 $3 polygrainvoice~ $0]
The first argument is for 'n', or the number of voices
desired, and is controlled by [polygrainsynth]'s second argument. High numbers
give rich polyphony, but can also use more CPU. Stick a CPU meter (like
[loadometer] in [polygrainsynth_example]) in your patch, and watch it to
see what kind of bandwidth you can get away with under various musical
"loads"; adjust 'n' accordingly in the creation argument for
[polygrainsynth]. Trying to suck more CPU cycles than exist will lead to "interesting
music", so be conservative with 'n'.
The next argument tells the internal [poly] whether to use
voice stealing, and is controlled by [polygrainsynth]'s third argument. Sometimes, setting
voice stealing to 0 (off) makes for fewer glitches in a complex voicing situation.
[polypoly] allows up to four more arbitrary arguments,
which are passed through to the object being cloned. [polygrainsynth]
currently uses only one of them to pass the $0 variable, which serves as a
unique prefix to communicate GLOBAL parameters to all [polygrainvoice~] voices of
this [polygrainsynth] instance.
When any message is sent to '$0-GLOBAL-allOff'
(OSC: /(synthname)/global/allnotesoff), all notes
currently attacked are released and deallocated from the
voice-managing [poly] object). This is useful for quashing stuck notes,
which can be caused by incomplete or unbalanced note messages sent to
[polygrainsynth] sends frequency/amplitude pairs to [polypoly]'s first inlet.
[polygrainsynth] makes good use of [polypoly]'s second inlet, packing a dozen
or so different synthesis parameters into it. At each note-on (i.e., each frequency
message, where amplitude > 0), this packed message is sent to the next allocated
[polygrainvoice~], where it is unpacked in the reverse order and applied to the
synthesis parameters right before the note is attacked. These are the "local" parameters,
[polygrainsynth] supports [sssad] state-saving. See the [polygrainsynth_example]
object for how to do it. Be aware that when you save a patch, a snapshot of the
current absolute path to the sound source file is embedded in the patch. This
may cause problems if that path somehow changes (you may have to hand-edit it).
Migrating v0.x presets and OSC to v1
This is an issue only for those who have made [sssad] presets with [polygrainsynth] of vintage
earlier than 1.0.
The [sssad] preset parameter called "ptrslider_enable" became "anchor" in v1.0., and acceptable values
became 0 (anchor off) and 1 (anchor on). You may either make this
change by hand with a text editor for all v0.x presets, or (if you have perl installed) run these perl
lines, in this order, from the command line in the directory or directories containing your pre-1.0 patches:
perl -pi -e 's/ptrslider_enable 0/ptrslider_enable 1/g' *
perl -pi -e 's/ptrslider_enable -1/ptrslider_enable 0/g' *
perl -pi -e 's/ptrslider_enable/anchor/g' *
(Make sure to include that final asterisk.)
v1.0 OSC change
The OSC node formerly known as /allNotesOff is now /allnotesoff (no more camel-case). If you used this
OSC node with an earlier version of [polygrainsynth], please change accordingly.
I built on a great deal of pre-existing Pd work to
make this synthesizer.
First, [polygrainsynth] would be an empty shell without Jamie Bullock's [a_grain]
granular synthesis abstraction, as well as the associated objects [a_rand] and
Frank Barknecht designed [polypoly] and [sssad] and
[singleton], among a multitude of other useful Pd objects, many essential
to this abstraction.
Chris McKormick's "s-abstractions" taught me how to do
GOP and more generally, how to organize my thinking about patches. I
learned how to use [sssad] state-saving from his examples, too.
(the creator(s) of svf~): this filter has a long
lineage, so I'm not sure who to thank, but it sounds beautiful and
Thanks to Steffen Leve Poulsen for giving me an idea for displaying
the selected area of the source table, and for his excellent assistance
beta-testing this release.
Areas for Improvement
Some ideas for future improvement:
I'd like to figure out an efficient way to allow inter-grain silence.
It would also be interesting to experiment with different grain evenlope shapes.
Carpe diem, hackor! I am very slow about making changes, so
don't wait for me! Make your own improvements/customizations if you have
some ideas -- or just use [polygrainsynth] as a template for something
Please let me know (via the "contact" link at the top
of the page) if you find any bugs or bad behavior. Also, I'd be glad to
hear if you got any good use from this synthesizer.
- 2012-01-22 - v 2.0
- Fixed glitch in quartic curve for multicurveadsr
- Fixed bug in gain changer inside multicurveadsr
- Generally cleaned up envelopes - no unwanted "snaps"
- Added OSC parameter: /pan/mode
- Made panning equal-power
- 2009-12-09 - v 1.2
- Fixed typo in message receiver for $0-GLOBAL-alloff message, which kept it from working
- Fixed bug in OSC-UI toggle (didn't work before).
- Changed all AD & R sliders on envelopes to log response, with bottom of 8
- 2008-11-24 - v 1.1
- Fixed bug that prevented proper setting of grain pointer on initial notes.
- 2008-11-22 - v 1.0
- Added "global" toggle, which enables live output of global-capable parameters to all voices.
- Added "OSC-UI" toggle, which enables the UI to follow OSC input.
- Fixed many small bugs in indeterminacy (rand and drunken walk).
- Fixed bug in pointer wrap function for negative pointer hops.
- Eliminated "pointer set" slider and variable for anchoring. Anchored notes
now start at the subsection beginning (for pointer hops greater than or equal to 0)
or the subsection end (for pointer hops less than 0).
- Eliminated "rand/drunk" switch to make "rand" and "drunk" controls less cumbersome.
Now, a zero value in these parameters is used to switch them off. This has the added
benefit of allowing complete independence between hop_size and grain_dur indeterminacy controls.
- OSC nodes are all-lowercase now (e.g. /allNotesOff is now /allnotesoff).
- 2008-10-02 - v 0.3
- Gain goes back to being an attack-set parameter, not a global one.
- 2008-09-23 - v 0.2
- Filled in missing branches on OSC tree.
- 2008-09-16 - v 0.1
The following files included with this distribution:
are licensed under the Creative Commons Attribution-NonCommercial-ShareAlike
License by Jamie Bullock. To view a copy of this license, visit
or send a letter to
Creative Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA.
Everything else in the polygrainsynth download is released under the same license as Pd,
with myself and others included as authors:
This software is copyrighted by Miller Puckette, Frank Barknecht, Phil Stone and others. The following
terms apply to all files associated with the software unless explicitly
disclaimed in individual files.
The authors hereby grant permission to use, copy, modify, distribute,
and license this software and its documentation for any purpose, provided
that existing copyright notices are retained in all copies and that this
notice is included verbatim in any distributions. No written agreement,
license, or royalty fee is required for any of the authorized uses.
Modifications to this software may be copyrighted by their authors
and need not follow the licensing terms described here, provided that
the new terms are clearly indicated on the first page of each file where
IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY
FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE
IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE
NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
RESTRICTED RIGHTS: Use, duplication or disclosure by the government
is subject to the restrictions as set forth in subparagraph (c) (1) (ii)
of the Rights in Technical Data and Computer Software Clause as DFARS
252.227-7013 and FAR 52.227-19.