P H I L    S T O N E

home music code contact

[polywavesynth] - a polyphonic synthesizer for Pure Data

polywavesynth.gif

[polywavesynth] is a ready-to-use, CPU-cycle-stingy, OSC-addressable polyphonic "wave" synthesizer for PD. The tag "wave" is used for want of a better (short) descriptor for the somewhat unusual mix of synthesis technologies employed, which is table-driven subtractive synthesis with a dash of FM.

[polywavesynth] is a front end/UI which directs control messages to multiple instances of [polywavevoice~], which is the synthesizer engine for each voice. It is theoretically possible to use a [polywavevoice~] on its own, but it would just be a mono synth with a somewhat cumbersome method of parameter addressing. Its real power comes from being invoked and managed by Frank Barknecht's [polypoly] object (see below), as is done by [polywavesynth].

You can hear some music I've made with this synthesizer here.

Setup

First, download the polywavesynth archive here.

If you are using PD-extended of the most recent vintage, you should be ready to go. Specifically, PD-0.39.3-extended or higher includes Martin Peach's OSC library. Download it here -- look for the correct version for your platform.

Otherwise, [polywavesynth] and [polywavevoice~] require two externals not found in vanilla PD:

  • cyclone/svf~ (state variable filter).
  • mrpeach/osc/* (Martin Peach's OSC objects).

[polywavesynth]'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).

[polywavesynth] Operation

It should be pretty easy to get a sound out of [polywavesynth]. See [polywavesynth_example] (which includes presets), or even just the help patch, to quickly make some noise.

Anything in [polywavesynth] displayed in color is an adjustable parameter, which can be saved and recovered using [sssad]. Clear number boxes are display-only. Most controls should be fairly obvious in function, with a few notes:

  • when 'Q' is set to zero, it switches enveloped low-pass filtering completely off.
  • 'modFreq' is interpreted differently, depending on whether "ratio" is on (it selects from a list of fixed ratios) or off (it is interpreted as an actual frequency).
  • the 'detune' value is in cents. It is particularly useful when ganging up multiple [polywavesynth]s for a very fat sound. It can also be used for transposition.
  • pan controls - see discussion of how panning works, below.
  • the 'lastKnob' display is ganged up to all the envelope sliders' outputs, and shows whichever one changed last (a space saving compromise).

N.B.: [polywavesynth] implements its wave tables in a singleton. Therefore, you can have multiple [polywavesynth]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 [polywavesynth], 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 [polywavesynth], and re-open -- the singleton will re-establish.

Specifications

Inlets:

  • left: frequency of note in Hertz; initiates attack or release
  • middle: amplitude of note, between 0 and 1 (>0 = attack, 0 = release)
  • right: OSC inlet (see OSC implementation, below)

Arguments:

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

Outlets:

  • left: audio left channel
  • right: audio right channel

OSC implementation tree:

  • /(synth name)
    • /gain --(value >= 0.0, though be careful with gain > 1.0)
    • /note
      • /freq ---(value in Hertz, will trigger or release note, so should be last parameter set)
      • /amp ----(amplitude multiplier, 0.0-1.0; 0=release >0=attack)
    • /osc
      • /carrier
        • /wave ----(0=sine 1=square 2=ramp 3=pulse 4=triangle 5=noise)
        • /detune --(value in cents)
      • /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)
      • /mod
        • /wave -------(0=sine 1=square 2=ramp 3=pulse 4=triangle 5=noise)
        • /amount -----(0.0 - 1.0, modulation index)
        • /freq -------(value in Hertz)
        • /ratOrFreq --(freq. mode switch 0=frequency 1=ratio)
    • /filt
      • /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)
      • /freq --(value in Hertz)
      • /q -----(0=filter switched off -- reasonable values=between 0.1 and 1.0)
      • /pan
        • /position --(0=full left; 1=full right)
        • /speed -----(speed of transition to new position, in msecs., >=2)
      • /global
        • /pitchbend ---(value in cents)
        • /allNotesOff --(any value)

How it works (the wonder of [polypoly])

[polywavevoice~], 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 [polywavesynth]:

[polypoly $2 $3 polywavevoice~ $0-GLOBAL]

The first argument is for 'n', or the number of voices desired, and is controlled by [polywavesynth]'s second argument. High numbers give rich polyphony, but can also use more CPU. Stick a CPU meter (like [loadometer] in [polywavesynthExample] 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 [polywavesynth]. Trying to suck more CPU cycles than exist will lead to "interesting music", so be conservative with 'n'. Anecdotally, on my MacBook Pro, sustaining 64 notes simultaneously uses about 75% CPU.

The next argument tells the internal [poly] whether to use voice stealing, and is controlled by [polywavesynth]'s third argument.

[polypoly] allows up to four more arbitrary arguments, which are passed through to the object being cloned. [polywavesynth] currently uses only one of them, which serves as a prefix to communicate to all existing instances of [polywavevoice~]. This argument is set to "$0-GLOBAL", which means receivers prefixed with "$0-GLOBAL" inside [polywavevoice~] can be accessed. Only two such receivers exist inside [polywavevoice~] currently, "-pbend" and "-allOff". The first allows pitch bend (in cents) sent to '$0-GLOBAL-pbend' (OSC: /(synthname)/global/pitchbend) to control all voices at once. 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 [polywavesynth].

[polywavesynth] sends frequency/amplitude pairs to [polypoly]'s first inlet. [polywavesynth] makes good use of [polypoly]s second inlet, packing twenty 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 [polywavevoice~], where it is unpacked in the reverse order and applied to the synthesis parameters right before the note is attacked.

[polywavevoice~] Architecture

At the heart of each [polywavevoice~] is a pair of table-driven waveform oscillators, in a carrier/modulator configuration. Both carrier and modulator can be switched between five simple waveforms (sine, square, pulse, sawtooth, triangle) or a random-filled "noise" table. Modulation may be switched between rational (modulator can be a harmonic of the carrier, like in most commercial FM synthesizers), and free (useful for inharmonic sounds, or at very low frequencies, vibrato.) Note that the "freq" value is interpreted differently for the two modes -- either as selecting from a list of fixed ratios, or as a literal frequency.

The output of this wave generator is run through an envelope-controlled, variable-frequency and Q-factor low-pass filter. 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.

[polywavevoice~] is fairly 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 [polywavevoice~] 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.

State Saving

[polywavesynth] supports [sssad] state-saving. See the [polywavesynthExample] for how to do it. Currently, parameters input via the OSC inlet are not passed through to the state-saving mechanism, nor do they update the sliders and other controls (though they do show up in the display boxes).

Credits

I built on a great deal of pre-existing PD work to make this synthesizer.

I owe big thanks to Tom Erbe and a couple of his students, who designed some wonderful synthesizer emulations back in 2005. One of these in particular, Patrick Sanan's Minimoog, inspired me to experiment with making my own PD synth. I started with the waveform data from Patrick's synth (which, with the exception of the triangle wave and noise, is bandlimited). Since his comments lead me to believe he got the table-generating code from Tom, I named my modification of this lifted code [erbe_tables] in Tom's honor.

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.

Frank Barknecht designed [polypoly] and [sssad] and [singleton], among a multitude of other useful PD objects. He makes magic happen in PD.

(the creator(s) of svf~): this filter has a long lineage, so I'm not sure who to thank, but it sounds beautiful and functions superbly.

Areas for Improvement

I can only stand designing instruments for so long, and then I need to play them, so I sometimes take expedient shortcuts to get something functioning. As a result, this synthesizer could use improvement in some areas.

  • Roman Haefeli's band-limited oscillators would be a worthwhile enhancement, and I may put them in eventually -- though the wiring is a little daunting. One of the many nice things about them is that they switch to non-bandlimited (simple) versions of the waveforms at low-frequency, which would be good for the lfo's mentioned below.
  • There's something funny going on with rational FM at low harmonic numbers; it sounds like the modulation comes and goes on various voices -- this can best be heard when repeating a note over and over. I have no idea what is causing this yet, but it actually sounds kind of interesting.
  • The [multicurveadsr] envelope gets the job done, and to my ears sounds good, but it's not very elegant inside. The math needs cleaning up. There's a small glitch (not anything I've been able to hear, but it's visible in graphs) in the onset of decay when using the quartic-like curve factor with certain slopes.
  • I'm only using a small part of [svf~]. More of its functionality should be exposed.
  • OSC and state-saving in [polywavesynth] are a bit "impedance-mismatched" right now, with some overlapping functionality. I suspect that there is better a way of integrating them, perhaps something more like Frank Barknecht's RRADical -- though I don't fully understand that system yet.
  • I'm interested in creating more /global parameters for live modulation of voice parameters in a given synth, and will probably play with that next. An lfo or two internal to each [polywavevoice~] would also be nice; perhaps used during sustain for vibrato, tremolo or other per-note modulations.

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 [polywavesynth] as a template for something completely different.

Revision History

  • 2007-10-27 - v 2.3
    • Gain parameter moved to [polyWaveVoice~], to prevent changes between notes.
    • Gain parameter made accessible through OSC.
  • 2007-09-29 - v 2.2
    • Fixed bug causing voice parameters to change on release during polyphony.
  • 2007-09-17 - v 2.1
    • fixed [polypoly] path issue; polywavesynth folder no longer needs to be in PD path.
  • 2007-09-16 - v 2.0
    • eliminated all global receives (except for SSSAD)
    • added OSC inlet and OSC addressing of all parameters
    • parameters now only changed on attack (used to be on release, too)
    • added more values for modulation ratios
    • included dependent abstractions in local "lib" directory
    • cleaned up panning considerably
    • exposed voice-stealing switch as a [polywavesynth] argument
    • made all objects lowercase...dedicated to frankbarknecht :-)
  • 2007-09-05 - First release
    • (named polyWaveSynth and polyWaveVoice~)

License

Everything in the polywavesynth download is released under the same license as PD, with me as author:

Copyright:

This software is copyrighted by 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
http://pkstonemusic.com