gen~ for Beginners, Part 3: Counting, and a World without bang Messages
For this installment of our tutorial series for the beginning gen~ programmer, we're going to continue to look at more common sources of anxiety: learning to work in the single-sample environment of the gen~ object. We're going to focus on two of the important differences: learning to count, and living in a world without the bang message. I've already mentioned that your gen~ object internally does its calculations at the rate of one sample at a time - that insight has some practical meaning as you learn to patch. Since everything in your gen~ patcher window is processed as samples, that means that you’re not only using samples as a unit of processing time, but you also will need to think of working with time in a gen~ patcher in terms of samples rather than the more common and comforting milliseconds you're probably used to as an MSP programmer.
Even though we're now working in an environment where we count in samples, the standard set of Gen operators we use have comfortingly similar names, and work in comfortingly similar ways: counter, +=, and *=. In this tutorial, we're going to spend our time with the Gen counter operator.
The counter operator is a simpler version of the one you already know, but you set the amount to be incremented with each sample’s worth of time. We’ll start by counting at the same rate that we process stuff inside of the gen~ patcher - one sample at a time.
The counter operator resembles its Max and MSP cousins in that we’re specifying a count maximum, but it’s got a few different tricks. For one thing, the counter is stopped and started by sending a non-zero value to its middle inlet. Since you and I tend to think of on/restart as a one rather than a zero, we’ll add a !- operator to the patch. This way, turning the counter “on” using a toggle/sig~ object pair in our gen~ patch will reset the counter and start the count (If we wanted a counter that could be turned on and off without resetting, we could hook the toggle up by itself to the left inlet and add the reset logic separately). We’ll be counting from 0 to 44099 in this patch.
Of course, you and I may not normally think of time in terms of samples - in the Max world, we’re either thinking in terms milliseconds or tempo, or perhaps even Max ITM values. When we learn to work in the gen~ patching environment, we may need to separate out the rate at which the gen~ patcher does its calculations from the rate at which we step through a buffer we’re playing back. But when it comes to milliseconds, there are a number of other tools available to us when working with buffer operators and thinking about playback and delay time. Where it makes the most sense, Gen operators such as the phasor work with frequency values, just like their MSP cousins. Similarly, you may be familiar with using the mstosamps object in the Max/MSP world to do your time to sample conversions. Gen gives you the mstosamps operator that does the same counting for you (of course, there's a sampstoms operator that does the opposite, should you need it).
Here’s a simple variation of the counter patch that lets us specify the count in milliseconds - we add a mstosamps operator to our patch, and we’re ready to go. Just in case you’d like to use your gen~ patcher with Max ITM time values, the example shows you how to do that, too - the Gen patching environment knows nothing about Max’s ITM features, so you’ll need to use a translate object to convert note values to milliseconds for input.
This patch also demonstrates a few other counter features, and how they’re used. The counter operator has a “hit maximum” output just like the counter object in Max, but it outputs a signal rather than a bang message. Our patch uses the MSP edge~ object to output bang messages that might be helpful to us in the Max world on the outside of the patcher. You’ll also notice that the third outlet provides us with something else similar to the counter object - a “carry count” that we can use for interesting things such as, say, multiple playback of fragments of a buffer operator’s contents. Stay tuned for that….
In both of the previous counting examples, there’s an additional detail to the counter operator inside of our patcher: we specify the increment used for our count. In the regular Max counter world, we always count in integers. This operator seems a bit more like an accum object in Max, doesn’t it?
There’s a very useful reason for having this inlet. To hear it in action, here’s a simple patch that users the counter operator to play back an audio file stored in a buffer.
All gen~ operators expect to work with the idea of a sample as the basic unit of counting, and of time, too - if you’re working with a buffer operator inside a gen~ patcher, then you not only think of its length in terms of samples, you can think of counting in samples as playing back the audio file back - one sample at a time.
How can you find out how many samples are in the buffer? Many gen~ operators themselves work with arguments and parameters based on samples - in the case of the buffer operator, its outlet broadcasts the length of the buffer (in samples) with each sample count, so you can use that value to set the upper bound for the counter. And when you think of it, counting one sample at a time for buffer playback plays the audio file back at its normal speed. If you send the counter operator a value greater to or less than one, you’ll change the playback rate, too. This Max patch does just that - it accepts a number in its second inlet that sets the “step” the counter uses. With a step value of one, we hear playback at the normal speed. Change that value to 1.5 and listen to what happens.
This example separates out the idea of calculation rate from playback speed. But we may also want to use another way of measuring time for playback: frequency.
The final example for this tutorial will provide another example of learning to separate the rate at which you count in a gen~ patcher (one sample at a time) from the amount you use to increment your progress through a buffer when doing playback. Instead of thinking of changing the increment value in a way that specifies the speed of playback with reference to the original file’s length, we’ll make use of both the samplerate constant and the buffer operator’s output to specify playback in terms of frequency. This example patch uses the fill message to load an MSP buffer~ object with a single cycle cosine waveform (click on the message box to load the buffer~, and then double-click on the buffer~ object to see the contents). When the buffer has finished loading, it sends a bang message from its right outlet. We’ll use that bang message to load the new single-cycle waveform into our gen~ patcher’s buffer operator as we did before. Instead of using a floating-point value between zero and some number to set playback speed, we’ll specify the frequency at which we’ll read the waveform. We do that by dividing the frequency (sent to the gen~ object’s right inlet) by the current samplerate and multiplying it by the number of samples in the buffer.
So here’s another example of learning how to separate time as a rate of sample calculation from the speed at which we access the contents of our buffer in terms of sample offset. Access to the current sample rate (as a constant value you can access in your gen~ patcher) and the buffer size in samples (broadcast by the buffer operator) can provide a useful way to specify the playback of a buffer’s contents in terms of frequency. As we work through this tutorial series, you’ll see these kinds of calculations again.
A World Without bang Messages
Now that you've got a handle on the act of counting, there's one more feature of life in the single-sample gen~ world that's going to be a little bit different - living in a world without the bang message.
Actually, there is an equivalent, when you think of it. In the gen~ world, we're operating one sample at a time, rather than at the event rates in a normal Max patch (where we using bang messages) or audio rates in MSP (where we can send values once per signal vector).
In the gen~ world, a pulse is the equivalent of a bang message - a transition from a zero value to any non-zero value that happens within the time frame of a single sample. The Gen environment uses those single-sample length pulses everywhere, and we deal with the idea of those pulses when we think of momentary input and output to and from gen~ objects. One simple way to send something like a bang message into your gen~ object is to use the MSP click~ object, as shown:
Note: When I started talking about a pulse, you might well have thought, "Aha! I know just the thing for producing pulses in MSP - the pulse-train generating pulse~ object!" Here's a tip for you, if you're that person: The pulse~ object doesn't actually output pulses, it outputs a square wave with a fixed period and a fixed phase. What you'll need is a single pulse at the start of the pulse train - and the addition of an MSP change~ and a signal comparison (>~ 0.) can do that for you.
Meet the latch
In addition to demonstrating the click~ object, this sample patch will introduce you to one of your new best friends in the Gen world: the latch operator.
When working with MSP in the audio-rate domain, you've probably used the sah~ operator to sample audio-rate inputs based on a threshold value. The latch operator is similar, but it doesn't use thresholds - it works solely with zero/non-zero inputs. Send any non-zero value to a latch object's right inlet, and any sample input to the left inlet will be passed on. When the latch operator receives its next zero input, it will continue to output the last value received in its left inlet (The default last value is 0. when you instantiate a latch operator, but you can change that by using the latch operator's @init attribute).
The example above demonstrates how to manually sample the random source, and the second example that shows how you can use a little MSP patching to drive your clicks automatically (You might want to experiment with using MSP to use ITM values to generate your clicks). This simple and straightforward no-fuss design is incredibly useful in a variety of ways. You've just seen one of the most straightforwardly useful features of the latch operator - to output values periodically at rates below the single sample rate at which Gen operates (for a great example of this technique in operation, take a look at the gen~.chaos.maxpat patcher in your Gen examples folder).
You'll see the latch used again and again in gen~ patches. The example patches folder available with this tutorial contains a little patch called latching_fun which ought to whet your appetite a little for some of the things you can do with just a few simple operators. Have fun, and see if you can work out what's going on in the patcher, too....
Working with Pulses
Inside your gen~ object, the single-sample pulse is in widespread use. Once you get the hang of this idea, a lot of things about working with logic inside your gen~ object become a lot simpler. In addition, you don't need to be performing rocket surgery to make some nice noise.
1. Routing operators use the pulse
the ? (comparator), and switch operators respond to pulses sent to their left inlet (Actually, they're the same operator with two different aliases, like the += and plusequals operators). When they see that pulse, non-zero values sent to the first inlet will pass anything received in the second inlet. A value of zero will pass anything received in the third inlet. So the pulse is a single-sample switch....
2. Pulses can reset an operator's state
Many of the operators we've seen already have used a pulse to reset an operator's initial state - the counter operator, the phasor operator, and so on. The per-sample appearance of a non-zero value is a powerful tool in your gen~ toolbox, even in the humblest of situations
3. Logic signals can be used to generate and compare pulses
As we've seen, most pulse work with values that are either zero or no-zero input. That true of inputs for Gen logic operators - the bool, not (!), and (&&) and or (||) operators.
And, as we've seen, operators can be used to generate (less than (<) equals (==), etc.) and to compare pulses: Your comparison generates a 0 or 1 - a pulse, if you will - that you can use for whatever you desire. Here's an example of some of one of those comparison operators - the not (!) operator. It's located right in the middle of the following example patch, helping to create a gen~ patcher that duplicates the modal functionality of the Max counter object. In the patch, we take the regular output of the counter operator you've seen throughout this tutorial and use it to construct the down and up-down modes that the counter operator lacks. The down mode is pretty easy - just pass the regular counter operator's output through a !- object. But how do we get our counter to finish counting up and then start counting down?
We'll take advantage of the counter operator's third outlet, which keeps count of how many times we've reached the max value. Happily, counting down will only happen on the even count values, so we use the remainder (% 2) operator and an not (!) operator to test whether we're going to switch those directions. The 0 or 1 value output of the not (!) operator is, in fact a pulse. It selects which input to the ? (conditional) operator, which acts as a switch to pass through to the selector operator - the third inlet to the selector now outputs the up-down we need.
Leaving the gen~ City Limits....
In addition to working as pulses as inputs, Gen operators can output pulses, too. If that output is sent to an out operator, you can use a scope~ object to view your output. If you need to derive bang messages from those outputs, you can add a little MSP logic to create those messages. Here's an example that uses rate operators in a gen~ patch to create a clock divider:
The contents of our gen~ patcher demonstrate that logic operators (in this case, the less than (<) operator send a pulse (think of it as a "message" composed of a number value of 1) when the output of the rate operators drops back to zero and the change operator reports that drop as a negative value (By the way - the change operator is the simplest way to convert any step signal into a series of pulses).
A clock multiplier would work in the same way - it'd just use different arguments to the rate operator. You might want to take a minute to see if you can create a clock multiplier/divider whose rate operator values you can set using param operators for input, just for run....
Before We Part....
I wanted to leave you something to reflect upon until next week's gen~ Anxiety Top Ten installment - a patch for study.
Here's an example of a gen~ patcher that uses many of the features we've discussed here as an emulation of something you use all the time, but whose subtlety may have been less apparent: the MSP gain~ object. I'm sure you use gain~ objects all the time to control levels. But the gain~ object also performs signal smoothing over time - while you move the faders, it's actually keeping track of the direction of travel and incrementing or decrementing its input while you work to produce smooth output. It's a deep and subtle MSP object, actually.
Here's a gen~ patcher that performs the same function as the gain~ object.
I expect that you'll see some things in the gen~ patcher to the right that you recognize now, and I'll bet you have an idea of why they're in the patch;
We're counting the smoothing time for the patcher in samples (mstosamps)
We then store the result in a latch operator that we periodically update when our audio input changes (the delta operator handles that).
That change from the delta operator is also sent to another latch, where it's used to calculate the amount of change to be applied to the input signal, based on the length of time over which we smooth the signal.
We've got a running count (+=) that's reset each time the audio input changes, too.
To assist in that calculation the history operator provides one sample worth of delay, and works to create a feedback loop we use in the delta object's latch calculations.
There's more going on, of course - but I hope it's not quite as scary as when we started out. Try the patch out, but take some time to see if you can work out what's going on inside this gen~ patcher - and feel free to use it in your own patches. See you next week!
P.S. Gratitude and a shout-out to my colleague Emmanuel Jourdan (il miglior fabbro, as T.S. Eliot would say) for his insights on that last gain~-style gen~ patch.
Alright, so I spent last night trying to work out what is happening in the last (gain~) emulator patch.
Here are some notes I am very unsure of, please correct me where I am wrong. Complete beginner here.Cheers, Mike.
- (delta) sends a pulse every time (train), our input signal, drops to 0 or rises to 1.
- As we can see, (delta) is being used as our master 'trigger' in this patch as it is triggering both (latch) objects and (+=).
Starting with smoothing time, we convert ms into samples (mstosamps).
We then store the smoothing time that has been converted to samples in a (latch) operator that we periodically update when our audio input changes - the (delta) operator triggers that.
The (-) (history) and (latch) combo generate a pulse of numbers between -1 and 1 triggered by (delta). This part of the patch represents our signal:
The output of this (latch) is a sample of the difference between the input signal (train) and the smoothed signal delayed by 1 sample thanks to (history). This gives us a constant reference/refresh for calculating the final output. - is this correct?
The pulse of numbers coming out of (latch) are then divided (/) by the set smoothing time (in samples) to scale down appropriately.
(+=) is a resetting running count that is always being compared to the smoothing time. It is being reset every time (delta) sends a pulse, which is whenever (train) drops to '0' or rises to '1' as mentioned above.
This running count is in turn fed into a (<) that acts as a threshold (that hinges on smoothing time) that decides whether the signal is heading to '0' and staying there for a period of time (or not). See below:
If the smoothing time (in seconds) is set high enough, in other words: greater than (1 minus Pulse Width) of our input signal (train), then (<) will always output '1', because input1 of (<) [aka our running count] will always be less than input2 of (<) [aka our smoothing time] - causing the signal to head the opposite way as soon as it peaks/troughs. Why does it head the opposite way?
This is because the '1' that we get from (<) is multiplied (*) by the result of the (history) reference calculation patch mentioned above- so a '0' is never outputted - resulting in a triangle wave - or a much smoother signal as we don't stall at '0' anymore and glide up instead. (Symmetry is implied).
If the Smoothing Time is less than the (1 - PulseWidth) of the original signal, then the (<) operator will output a '0', that then gets multiplied by the (history) calculation. This means for a brief period of time, we multiply by 0 (resulting in a brief stream of 0s). This period of time is the difference between (1 - PulseWidth) and the Smoothing Time.
Smoothing Time = 400 ms
PW = 0.5
1 - 0.5 = 0.5 (*1000 to scale up to ms)
500 - 400 = 100 ms
So if smoothing time is set to 400 ms at a PW of 0.5, then (<) will output a stream of '0's for 100 ms or 4410 samples (given a 44.1kHz SR).
This means that our reference calculation signal now gets multiplied by '0' for a brief period of time (100ms in this example), resulting in stalling at '0' for 100ms or 4410 samples.
This is observable on the scope.
Smoothing Time= 200 ms
PW = 0.75
1 - 0.75 = 0.25 (*1000 to scale up to ms)
250 - 200 = 50 ms
So if smoothing time is set to 200 ms, at a PW of 0.75, then (<) will output a stream of '0's for 50 ms or 2205 samples (given a 44.1kHz SR).
This means that our reference calculation signal now gets multiplied by '0' for a brief period of time (50ms in this example), resulting in stalling at '0' for 50 ms or 2205 samples.
This is observable on the scope.
So to get a perfect triangle wave you would have to set Smoothing Time to at least (1.001 - PulseWidth) *1000 (ms).
In our example of PW= 0.5, anything above 500ms would result in a triangle.
Essentially we have crated a patch that 'resamples' our square wave based on a set threshold that decides whether the signal stays or doesn't stay at 0 or 1 or anywhere in between aka Smoothing!
If you read this far, please let me know if I'm looking at this the right way or if I'm completely off!!