Tutorials

Polyphonic Synthesizer Tutorial

Ever wanted to play multiple notes (voices) of your synthesizer or sampler at the same time? In Max, this is accomplished by using the poly~ object. In this video tutorial, we'll get you up to speed on how to use poly~, so you can start making your own polyphonic instruments.

- Download the patches

by Les Stuck on May 5, 2011

Stephen Taylor's icon

This is a fantastic tutorial - I had never thought of using swap and edge~ within poly~, thanks a lot!

tommy's icon

great! more tutorials please! thx

saturnin79's icon

Great tutorial, but what about making the final patch downloadable so that we can study it further ?

Ben Bracken's icon

Hi Jeepee,

Here is a link to these two patches:

Enjoy!
-Ben

fleeger's icon

Very clear! Excellent tutorial. Thanks tons!

PapyLives's icon

Thanks for this tutorial !
When an another one ???

lauweo1989's icon

Thanks you!!!

Richard's icon

Hey! Very excellant tutorial. Professional and clear. You should definitely do more! Thanks.

jhaysonn's icon

great tutorial man. bravo

lucycle's icon

I've learn many things through this tuto, which is great indeed!
I have nonetheless several problems :
1° the changes in the saved subpatch weren't always taken into account immediately in the main patch.
2° It doesn't work with a cycle~ instead of the saw~: the synth clips when the keys are released.
3° When I use the synth with a live.step sequence and through a makenote, I have tone bends when notes are changed.
I still find this tuto is great, but I have some further work to do to have my own synth module.
Thank you!

irvn's icon

@2:22 the divider /127 is not created.. or at the least it is not evident what the instructor is doing
I am using MAX6 and there is no /1`27. object that i can find..
that works anyway
Can not the lesson be just a little less arcane for idiots like me?

irvn's icon

the sub-patch does NOT open upon double click, nor does a separate window open after selecting and "encapsulating"
is this a preference, am I an idiot, or can you at the least explain without the glaring gaps in this tutorial?

UncleChubb's icon

@1diot8ox;
I had the same problems. Through extensive trial and error, I found the solution to our problems. @2:22, you need to create a new object, and simply type in "/ 127." (no quotes). to open the sub patch, right click and de-select "edit". then, you can double click the box that says p and the sub-patch will open in a new window.

Les Stuck's icon

Thanks everyone for the good feedback. It looks like UncleChubb figured out that I was making a "/ 127." object at 2:22. Yes, the space between the object's name (/) and its argument (127.) is important. UncleChubb also figured out that the patch needs to be taken out of Edit mode in order to double-click a patcher to open it. If you're in too much of a hurry to switch modes, you can just command-click (Mac) or control-click (Win) to operate a patcher when it's in Edit mode.

Lucycle, keep in mind that the synth_voice patch must be saved to disk before the changes are loaded into poly~. I tried swapping a cycle~ for the saw~ and didn't hear any problems. Also I tried feeding MIDI notes from live.step to the synth and didn't hear any portamento happening. Hmmm...maybe there's something else going on?

Sorry some things weren't clear. It's tough being really clear without making the tutorial ending up being an hour long. :) I'll definitely keep your comments in mind for the future. Happy patching!

Rokwatnou's icon

guys, i am really frustrated... whenever i try to rename the patch p into the name synth_voice it does not seem to accept it. I saved it in one folder and checked the filenames. Furtheron, on one pc it works when i leave it as 'p synth_voice' and i can add the poly~ infront, however on the othermachine, as soon as i change it to poly~ it opens up an empty patch! It seems so easy but i am doing something wrong...

Les Stuck's icon

It sounds like your patch is having a hard time finding synth_voice.maxpat. In the tutorial, both the main synth_patch.maxpat and synth_voice.maxpat are on the Desktop, in other words, in the same folder. Here's something you can try: make sure both patches are in the same folder, then close them both and reopen synth_patch.maxpat. Does it find synth_voice.maxpat?

Keep in mind also that "p synth_voice" is a subpatch; its contents are part of the main patch. Once you drop the p, both "synth_voice" and "poly~ synth_voice" load the file "synth_voice.maxpat" from disk. For that reason, it must be in a location that Max can find.

If you're really frustrated, go to menu item Options > File Preferences… to add a new folder for Max to search.

R's icon

thanks a lot for you reply Les Stuck, now i am pretty sure it is a bug. I created a new folder into which i save all patches and sometimes it works and sometimes when i rename a new object to the patches name saved earlier it opens up a new patcher window. Then i copy paste the content of the patch but saving again gives me the obvious 'there is already a file with that name' dialogue.
It is funny that once the containt is copied across it acts like the patcher is supposed to act ( i can see all the in and outs but once i create a new object again and copy the exact name of the patcher into the new object it opens up an empty patcher.
Little note, i am using maxforlive but that would not be a problem right?

Thanks in advance

Logan's icon

R: Little note, i am using maxforlive but that would not be a problem right?

Exactly the problem I had. I made sure i was putting the synth_voice file in the same folder as the patcher in live (I did Show in Finder), it did nothing. It worked fine when I open the same patcher without running ableton and do it with both files on the desktop. I guess theres another step to it when you are using m4l? i'm a little bit new to this, so I don't know what that might be.

Dan Shute's icon

Hey
I know I'm probably doing something really stupid, but my outlet on the 'poly~ synth_voice 1 @steal 1' box refuses to show, even after I save the new sub patch with the new 'in 1' and 'out~ 1'. Any thoughts?
Cheers.

Morgan Rodabaugh's icon

Why does this patch not work using a Novation Launchpad?

brendan mccloskey's icon

I predict that this question will fall off the end of the interwebz

MakePatchesNotWar's icon

"Why does this patch not work using a Novation Launchpad?"

I can confirm here. Not working with 6.latest either...

mohamad kassem's icon

i have used your method but changed the saw~ object to a wavetable with an oscillator but cant get any polyphony and ideas ?

Screen-Shot-2015-08-16-at-00.31.58.png
png
Ullstein's icon

thanks for the very helpful tutorial. Unfortunately I cannot open the original patch when option clicking the title bar of the instance in poly~ . Any idea what the reason for that could be? Thanks

Hans Peter

Ben Bracken's icon

Hi Hans Peter,

In Max 7, I would recommend using the 'Patcher Windows' icon in the lower left patcher toolbar. This should show you the patcher hierarchy.

-Ben

JD95's icon

I've copied this tutorial exactly and no outlet ever appears on the "poly~ synth_voice 16 @steal 1" patch. Any idea why this is? Thanks.

Ben Bracken's icon

Hi James,

Do you see any errors in the Max window?

Is the synth_voice.maxpat patcher in Max's search path? It will have to be in order for poly~ to find it.

What happens when you double-click on the poly~ in a locked patcher?

-Ben

Alexander Shanov's icon

Hey James Dalgleish and Dan Shute,

I encountered the same issue but managed to solve it in a very simple way.

After saving the sub-patch, restart the main patch and they should appear. You could also add the file extension after sub-patch name, it might make it easier for Max to read/find the proper files, so for instance: [poly~ adsrsubpatch.maxpat 16 @steal 1].

This should solve your problem but if it doesn't, check and see if you have saved both the main and sub-patches in the same directory (folder/location) on your computer.

Also to whoever cant access the sub-patch, your patcher needs to be locked in order to do so!

Cheers!

Alexander Shanov's icon

Hey James Dalgleish and Dan Shute,

I encountered the same issue but I managed to solve it in a very simple way.

After saving your sub-patch restart the main patch. That should make the inlets appear. Additionally you could add the file extension to "poly~", as it might help Max find/read the file.

E.g. [poly~ adsrtest.maxpat 16 @steal 1]
If this doesn't work, check if you have saved both the main and sub-patch in the same directory(location/folder) on your computer.

Also, whoever can't open their sub-patches, the main patch needs to be locked in order to do so.

Cheers!

Matt Laming's icon

Hi, I've tried this and it works fine when the cycle is inside the poly~ but not when its outside? How can I fix this because I require it to be outside.
{
    "boxes" : [         {
            "box" :             {
                "maxclass" : "number",
                "id" : "obj-6",
                "outlettype" : [ "", "bang" ],
                "patching_rect" : [ 302.0, 376.0, 50.0, 22.0 ],
                "style" : "",
                "numinlets" : 1,
                "parameter_enable" : 0,
                "numoutlets" : 2
            }

        }
,         {
            "box" :             {
                "maxclass" : "ezdac~",
                "id" : "obj-5",
                "patching_rect" : [ 485.5, 574.0, 45.0, 45.0 ],
                "style" : "",
                "numinlets" : 2,
                "numoutlets" : 0
            }

        }
,         {
            "box" :             {
                "maxclass" : "newobj",
                "text" : "*~",
                "id" : "obj-4",
                "outlettype" : [ "signal" ],
                "patching_rect" : [ 509.0, 339.0, 29.5, 22.0 ],
                "style" : "",
                "numinlets" : 2,
                "numoutlets" : 1
            }

        }
,         {
            "box" :             {
                "maxclass" : "gain~",
                "id" : "obj-2",
                "outlettype" : [ "signal", "int" ],
                "patching_rect" : [ 497.0, 407.0, 22.0, 140.0 ],
                "style" : "",
                "numinlets" : 1,
                "parameter_enable" : 0,
                "numoutlets" : 2
            }

        }
,         {
            "box" :             {
                "maxclass" : "newobj",
                "text" : "cycle~",
                "id" : "obj-1",
                "outlettype" : [ "signal" ],
                "patching_rect" : [ 397.0, 295.0, 45.0, 22.0 ],
                "style" : "",
                "numinlets" : 2,
                "numoutlets" : 1
            }

        }
,         {
            "box" :             {
                "maxclass" : "newobj",
                "text" : "poly~ PLYPHONIC 16 @steal",
                "fontname" : "Arial",
                "id" : "obj-152",
                "outlettype" : [ "signal", "" ],
                "patching_rect" : [ 447.75, 195.832977, 169.0, 22.0 ],
                "style" : "",
                "numinlets" : 5,
                "fontsize" : 12.0,
                "numoutlets" : 2
            }

        }
,         {
            "box" :             {
                "maxclass" : "message",
                "text" : "midinote 70 0",
                "linecount" : 2,
                "fontname" : "Arial",
                "id" : "obj-138",
                "outlettype" : [ "" ],
                "patching_rect" : [ 169.036987, 190.332977, 50.0, 36.0 ],
                "style" : "",
                "numinlets" : 2,
                "fontsize" : 12.0,
                "numoutlets" : 1
            }

        }
,         {
            "box" :             {
                "maxclass" : "dial",
                "varname" : "S[1]",
                "id" : "obj-129",
                "degrees" : 300,
                "outlettype" : [ "float" ],
                "patching_rect" : [ 628.120972, 78.795998, 40.0, 40.0 ],
                "size" : 1.0,
                "style" : "",
                "numinlets" : 1,
                "presentation" : 1,
                "parameter_enable" : 0,
                "presentation_rect" : [ 1144.333374, 528.0, 40.0, 40.0 ],
                "floatoutput" : 1,
                "numoutlets" : 1
            }

        }
,         {
            "box" :             {
                "maxclass" : "dial",
                "varname" : "R[1]",
                "id" : "obj-130",
                "degrees" : 300,
                "outlettype" : [ "float" ],
                "patching_rect" : [ 674.120972, 78.795998, 40.0, 40.0 ],
                "size" : 3000.0,
                "style" : "",
                "numinlets" : 1,
                "presentation" : 1,
                "parameter_enable" : 0,
                "presentation_rect" : [ 1188.333374, 528.0, 40.0, 40.0 ],
                "numoutlets" : 1
            }

        }
,         {
            "box" :             {
                "maxclass" : "dial",
                "varname" : "D[1]",
                "id" : "obj-131",
                "degrees" : 300,
                "outlettype" : [ "float" ],
                "patching_rect" : [ 579.120972, 78.795998, 40.0, 40.0 ],
                "size" : 1000.0,
                "style" : "",
                "numinlets" : 1,
                "presentation" : 1,
                "parameter_enable" : 0,
                "presentation_rect" : [ 1100.333374, 528.0, 40.0, 40.0 ],
                "numoutlets" : 1
            }

        }
,         {
            "box" :             {
                "maxclass" : "dial",
                "varname" : "A[1]",
                "id" : "obj-132",
                "degrees" : 300,
                "outlettype" : [ "float" ],
                "patching_rect" : [ 517.25, 65.486, 40.0, 40.0 ],
                "size" : 3000.0,
                "style" : "",
                "numinlets" : 1,
                "presentation" : 1,
                "parameter_enable" : 0,
                "presentation_rect" : [ 1056.333374, 528.0, 40.0, 40.0 ],
                "numoutlets" : 1
            }

        }
,         {
            "box" :             {
                "maxclass" : "newobj",
                "text" : "prepend midinote",
                "fontname" : "Arial",
                "id" : "obj-27",
                "outlettype" : [ "" ],
                "patching_rect" : [ 258.0, 115.000992, 134.0, 27.0 ],
                "style" : "",
                "numinlets" : 1,
                "fontsize" : 16.0,
                "numoutlets" : 1
            }

        }
,         {
            "box" :             {
                "maxclass" : "newobj",
                "text" : "pack",
                "fontname" : "Arial",
                "id" : "obj-107",
                "outlettype" : [ "" ],
                "patching_rect" : [ 40.549004, 86.795998, 44.0, 27.0 ],
                "style" : "",
                "numinlets" : 2,
                "fontsize" : 16.0,
                "numoutlets" : 1
            }

        }
,         {
            "box" :             {
                "maxclass" : "newobj",
                "text" : "notein",
                "fontname" : "Arial",
                "id" : "obj-110",
                "outlettype" : [ "int", "int", "int" ],
                "patching_rect" : [ 68.5, 20.5, 54.0, 27.0 ],
                "style" : "",
                "numinlets" : 1,
                "fontsize" : 16.0,
                "numoutlets" : 3
            }

        }
],
    "lines" : [         {
            "patchline" :             {
                "source" : [ "obj-4", 0 ],
                "destination" : [ "obj-2", 0 ],
                "hidden" : 0,
                "disabled" : 0
            }

        }
,         {
            "patchline" :             {
                "source" : [ "obj-27", 0 ],
                "destination" : [ "obj-152", 0 ],
                "hidden" : 0,
                "disabled" : 0
            }

        }
,         {
            "patchline" :             {
                "source" : [ "obj-27", 0 ],
                "destination" : [ "obj-138", 1 ],
                "hidden" : 0,
                "disabled" : 0
            }

        }
,         {
            "patchline" :             {
                "source" : [ "obj-2", 0 ],
                "destination" : [ "obj-5", 0 ],
                "hidden" : 0,
                "disabled" : 0
            }

        }
,         {
            "patchline" :             {
                "source" : [ "obj-2", 0 ],
                "destination" : [ "obj-5", 1 ],
                "hidden" : 0,
                "disabled" : 0
            }

        }
,         {
            "patchline" :             {
                "source" : [ "obj-152", 1 ],
                "destination" : [ "obj-6", 0 ],
                "hidden" : 0,
                "disabled" : 0
            }

        }
,         {
            "patchline" :             {
                "source" : [ "obj-152", 0 ],
                "destination" : [ "obj-4", 0 ],
                "hidden" : 0,
                "disabled" : 0
            }

        }
,         {
            "patchline" :             {
                "source" : [ "obj-152", 1 ],
                "destination" : [ "obj-1", 0 ],
                "hidden" : 0,
                "disabled" : 0
            }

        }
,         {
            "patchline" :             {
                "source" : [ "obj-132", 0 ],
                "destination" : [ "obj-152", 1 ],
                "hidden" : 0,
                "disabled" : 0
            }

        }
,         {
            "patchline" :             {
                "source" : [ "obj-131", 0 ],
                "destination" : [ "obj-152", 2 ],
                "hidden" : 0,
                "disabled" : 0
            }

        }
,         {
            "patchline" :             {
                "source" : [ "obj-130", 0 ],
                "destination" : [ "obj-152", 4 ],
                "hidden" : 0,
                "disabled" : 0
            }

        }
,         {
            "patchline" :             {
                "source" : [ "obj-129", 0 ],
                "destination" : [ "obj-152", 3 ],
                "hidden" : 0,
                "disabled" : 0
            }

        }
,         {
            "patchline" :             {
                "source" : [ "obj-110", 0 ],
                "destination" : [ "obj-107", 0 ],
                "hidden" : 0,
                "disabled" : 0
            }

        }
,         {
            "patchline" :             {
                "source" : [ "obj-110", 1 ],
                "destination" : [ "obj-107", 1 ],
                "hidden" : 0,
                "disabled" : 0
            }

        }
,         {
            "patchline" :             {
                "source" : [ "obj-107", 0 ],
                "destination" : [ "obj-27", 0 ],
                "hidden" : 0,
                "disabled" : 0
            }

        }
,         {
            "patchline" :             {
                "source" : [ "obj-1", 0 ],
                "destination" : [ "obj-4", 1 ],
                "hidden" : 0,
                "disabled" : 0
            }

        }
],
    "appversion" :     {
        "major" : 7,
        "minor" : 2,
        "revision" : 4,
        "architecture" : "x64",
        "modernui" : 1
    }

}

Matt Laming's icon

Hi, Ive tried this and it works when the cycle is inside the poly~ but not when its outside. How can I fix this I need it outside.

poly.maxpat
Max Patch
vichug's icon

what are youtrying to achieve Matt ? the very essence of the poly~ is to multiply automatically the number of simultaneous voices of whatever you put inside the poly. So if you put something outside of it, you won't have polyphony.
That said, if you want to share a patch using poly~, you need also to provide the abstraction that's inside the poly - remember it's a separate file

Ernest's icon

voice clipping is actually very difficult to avoid. Another problem is that transpositions affect all the playing notes, rather than just new notes. I designed a patch to overcome these problems, which is part of my larger work Godel. You can find the simpler version here:

vichug's icon

Hey Ernest, your page displays an error 403 (forbidden) for me

Sonia Beckwith's icon

Hello,

I am trying to use [key] and [keyup] to trigger this patch instead of note in. I created a set of sine frequencies that relate to the keys on my computer keyboard.

I am very new to max so please excuse my lack of know-how, any help is much appreciated!

Matthew Grouse's icon

Thanks for this! I'm writing a piece that uses two keyboards (using cycle~ instead of saw~)

Any ideas how I can have one of the keyboards at equal temperament and then keyboard 2 an exact replica but every pitch 'tuned down' a quartertone? e.g C1 on keyboard 1 is mirrored as C1/4b1 on keyboard 2.

. Also, any ideas on getting rid of clicks? It's particularly bad when using cycle~ instead of saw~

Cheers!

Noah Mcrobbie's icon

I've been trying to do this with key and keyup objects to mapped to MIDI numbers and then sent through poly~ but it seems that once i try any sort of polyphony, at least one of the notes I've pressed never shuts off.
This is also part of a microtonal synthesizer that I am building in Max and I have found that once i run it through poly~, even if I have pack and unpack set to send and receive floats (for the quatertones), it doesn't recognize the note values in between frequencies.

nm_microtone_2.maxpat
Max Patch

nm_synthvoice.maxpat
Max Patch

(yes, i'm aware my key and keyup patching is a total mess to look at)
If anyone could help me with this, it'd be greatly appreciated.

Andrew Yatsuhashi's icon

How can you change the adsr parameters from the main patch?

PSG's icon

Can you think of any reason why thispoly~ won't create polyphony? I followed your tutorial and made your little poly synth work. But when I tried to apply the same method to an already complex mono synth I had created, the thispoly~ won't make it a polyphonic synth. I have loaded 6 instances of the synth patch. The thispoly~ outputs do confirm that. It is hooked up properly to an ADSR inside the synth voice. The ADSR works because I can hear it working. Still, thispoly~ will *not* make polyphony for me. I have many different ADSRs doing different things at different places within my original synth patch. Does that have anything to do with it? I tried recreating the thispoly~ many times and hooking it up to different ADSRs within the patch, all to no avail.

Roman Thilenius's icon


nobody said thispoly~ would have to do anything with polyphony.

👽'tW∆s ∆lienz👽's icon

(dang, sounds like they could've used some Izotope DeEsser back in 2011
0:57 - "sssssssssingle patch chord for note messsssssssagesssssssssssssss"🐍🙉😁😇)

@PaulSanGregory
thispoly~ is used to keep track of individual voice status, and change mute/busy status. yes, if you have many ADSRs, it might be the place to look, but mainly, if you're registering 'busy'-state with a signal from adsr~ as in the video, it might not be working if the particular ADSR you hook it up to does not release all the way back down to 0(another way to ensure everything works is to send "mute $1" and "busy $1" messages to thispoly~ explicitly, according to exact millisecond durations of notes)... you might want to go through the original documentation/reference tutorials on polyphony to complement what you learn from the video in order to have a more thorough grasp of how to accomplish polyphony in these different contexts:
https://docs.cycling74.com/max8/tutorials/11_polychapter01
(or you could start another thread and post your patch there, people can take a look and give you specifics)

@AndrewYatsuhashi
you can attach 'receive' objects to the parameters of adsr~ within your poly~ patch, then in the main patch use 'send' objects of the same name to control those parameters(if you wanted to do this individually by voice, you could use the voice-specific 'target' messaging described in the poly~ helpfile under the 'voice messages' tab there)

PSG's icon

As you suspected, Raja, it was the "sssssssssingle patch chord for note messsssssssagesssssssssssssss" thing. Believe it or not, I had noticed that part already and tried wiring up with a single patch chord at one point, but something else must have been out of whack at the time because it still didn't work for me then. I tried it again just now, though, and it seems to work. Hopefully I'm not currently experiencing a fluke (!). Thanks for your reply...