First off, the output section has been changed slightly to include a clip~ object. This allows us to prevent sending the sound card anything greater that the normal -1.0 to 1.0 range. This object will clip the audio to that range, and will keep us away from unwanted distortion and clipping at the outputs. This is a good practice, and will be expanded upon in future articles.
The second, and more important, change is the section labeled "Envelope/VCA Stuff". This section, which only contained simple divider and multiplication object. Now, we are given two controls (for attack and release time), and everything is funneled through an object called ddg.velamp. This object is an encapsulation of some more meaty logic.
Let's pop open the ddg.velamp object and see what is in there...
This object takes three inputs - the note velocity, an attack time and a release time. On the other end, it outputs an envelope signal (you can tell it's a signal by the "fuzzy" patch cable). In between the inputs and outputs, we have to determine trigger and release points, create an envelope and create a velocity-scaled output.
The pink section is a simple evaluation of attack or release events. When a velocity value is input, it is checked to determine if it is greater than zero. In Max world, velocity of 0 is a note-off (or release), while a note-on is greater than 0 (i.e., the attack event). Using a route object, a bang is triggered from the appropriate output.
The green section is the setup for the envelope. First, you will see that the incoming attack and release time values are multiplied by 20 to stretch them out. In the synth patch itself, we are using the default 0-to-127 range for the dial objects. With this multiplication, we will now have ranges from 0 to 2540 ms.
Next, the attack and release values are used to create messages to be sent to the line~ object. The format of a line~ message is "level ", where time is optional (and defaults to 0 ms). On the left side of this section, we are setting up a message that will "zero out" the level, then build it up to 1.0 at the selected rate. In line~ terms, this is two seperate messages. So, we create a message box that contains two messages, using the comma (,) delimiter.
The funny message that is constructed is a convoluted way of getting the message "0., 1. " to the next message box. This is made somewhat more complicated by the difficulty in creating a message with a comma in it. In order to pass a comma in the message, we need to use the "" slash in front of the comma. Also, make sure that there is a space on both sides of the "," combo - otherwise the slash will be included as part of the prior or next message.
This message "sets" a message box that will be "banged" by the next incoming note-on message. A corrolary is available for the note-off, but doesn't have to set up the dual message - and is therefore much simpler. Try to carefully follow this patch to understand this routing.
The output of the line~ object is a ramping signal that runs from the last setting to the new setting. In order to use this output, as well as to still maintain velocity control, we need to scale the line~ output by the incoming velocity. To do this, we capture the note-on velocity (by ignoring note-offs), scale it using the divide (/) object, then multiply the line~ output by this value. In this way, the output of this sub-patcher will be sensitive to envelope settings as well as velocity.
The final step is the use of the rampsmooth~ object. This object is super valuable - it prevents ramped values (like amplitude envelopes) from changing too quickly. any signal (even our simple sine wave) will thump if the attack or release times occur too quickly. This object will help alleviate thumping by smoothing any change over a specified number of samples. There is still the opportunity for thumps - especially if you are driving the outputs very hard. But, in general, a simple rampsmooth~ strapped across a line~'s output will help create a more controllable envelope.
In the main patch, you can see that the velocity, attack time and release times are routed to the ddg.velamp object, and the output is sent to a multiplication (*~) object for output scaling. In this way, we maintain all of the VCA-style control of the original synth, with the new attack and decay times.
Next in the series - basic filtering.