PHIL STONE

[polygrainsynth]

a polyphonic synthesizer for Pure Data

Version 4.0

polygrainsynth.gif

[polygrainsynth] is a ready-to-use, OSC-addressable polyphonic granular synthesizer for Pd. Its engine is a modified version of [a_grain], 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").

Download and Setup

Download the polygrainsynth archive here.

After downloading, double-click on the .tgz file to produce the 'polygrainsynth' folder.

(Pd-extended users only: Pd-extended is no longer supported by new versions of [polygrainsynth]. You may download the last version compatible with Pd-extended at this link.)

Pd requires 2 external libraries to use this object: mrpeach and ggee.

If you don't have these libraries installed in your copy of Pd already, use the "Help->Find externals" menu item in Pd to download and install them. Be sure to pick the correct version for your platform, and be sure the libraries have been added to your search path (this is done automatically by "Find externals" if you have the "Should newly-installed libraries be added to Pd's search path?" item checked in the Preferences to this dialog).

NOTE: in the unlikely case that you are currently a user of a very early version of [polygrainsynth] (before version 1), be aware that there are slight differences between version 1 (and later) presets and those of version 0. See here for more information.

[polygrainsynth] Operation

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 get a sound out of [polygrainsynth] if everything is loaded correctly. Open the "polygrainsynth" folder created above, and double-click polygrainsynth_example.pd to get started.

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.

basic controls

granular synthesis controls:

There are five major areas of control over the granular synthesis engine:

More about sound array control

More about pointer control

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 is 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, and listen.

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.

Interface

Inlets:

  1. frequency of note in Hertz; initiates attack or release
  2. amplitude of note, between 0 and 1 (>0 = attack, 0 = release)
  3. external "mute" - note-ons are ignored when this is non-zero
  4. path to sound file
  5. OSC inlet (see OSC implementation, below)

Arguments:

  1. synth name, used for OSC addressing and preset saving/loading (allows multiple polygrainsynths to be used simultaneously yet controlled independently)
  2. number of voices
  3. voice stealing (0=off; 1=on)
  4. size of sound array, in bytes

Outlets:

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)
    • /global
      • /pitchbend ----(value in cents)
      • /allnotesoff --(any value)
    • /note
      • /freq ---(value in Hertz, triggers or releases note, so should be sent last)
      • /amp ----(amplitude multiplier, 0.0-1.0; 0=release >0=attack)
    • /grain
      • /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 drunk-walk deviation)
      • /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)
    • /filt
      • /freq --(value in Hertz)
      • /q -----(0=filter switched off -- reasonable values=between 0.1 and 1.0)
      • /env
        • /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)
    • /pan
      • /position --(0=full left; 1=full right)
      • /speed -----(speed of transition to new position, in msecs., >=2)
      • /mode ------(0=random; 1=fixed)

[polygrainvoice~] Architecture

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.

Polyphony

[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].

[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, discussed above.

State Saving

[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.

Credits

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 [a_drunk].

Frank Barknecht designed [polypoly] and [sssad] and [singleton], among a multitude of other useful Pd objects, many essential to this abstraction.

Thanks to Hans-Christoph Steiner for his equal-power panning code.

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.

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 highly encourage you to make your own improvements/customizations if you have some ideas -- or just use [polygrainsynth] as a template for something completely different.

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.

Revision History

License

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 here 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:

Copyright:

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 they apply.

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 MODIFICATIONS.

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.

Phil Stone
https://www.pkstonemusic.com