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 common sources of anxiety associated with 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.
Counting Out Time
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'll spend some 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. Let's start by counting one sample at a time - at the same rate that we process stuff inside of the gen~ patcher.
The counter operator resembles its Max and MSP cousins in that we specify a count maximum, but it has 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 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 ofmilliseconds or tempo or perhaps even Max ITM values. When you learn to work in the gen~ patching environment, you may need to separate out the rate at which the gen~ patcher does its calculations from the rate at which you step through a buffer you are playing back.
When it comes to milliseconds, there are several tools available to us in the Gen environment 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.
- 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 is a simple variation of the counter patch that lets us specify the count in milliseconds rather than samples - all you need to do is to add a mstosamps operator to the patch and you are ready to go. Suppose you want to use your gen~ patcher with Max ITM time values. In that case, 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 demonstrates a few other features of the counter operator 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 MSP world on the outside of the patcher.
- The third outlet of the counter operator provides us with something else similar to the counter object: a “carry count” that we can use for interesting things such as playing back multiple fragments of a buffer operator’s contents (Stay tuned for that).
In both of the previous counting examples, there is 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.
There is a very useful reason for having this inlet. To hear it in action, here is 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 operator.
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.
What happens if we want to play audio files at different speeds rather than the original sampled rate? Changing the playback speed is a matter of how much we increment the counter - if our increment is set to 2, the playback would run at double speed, and an increment 0.5 would play back at half speed. There’s no problem as long as our increment is an integer value. But if the increment is a floating-point value, we’ll have a problem.
Let’s suppose that our increment value is 1.5. Which sample slice should the peek operator fetch from the buffer operator? In its default form, the peek operator can only read a whole number (integer) position, and the fractional information will be ignored. This will cause an audible distortion (a kind of aliasing or nonlinear harmonic distortion).
To get a smoother readback from the buffer operator, we’ll need to estimate what the waveform’s position would be between sample frames. This process is usually referred to as interpolation. We can use an attribute to set the peek operator so that it will interpolate between the buffer value it fetches, and the value of the previous sample.
Our example patch does just that by using the @interp attribute, and an argument that specifies the kind of interpolation we want to perform (in this case, it’s a simple linear interpolation). Adding a peek myfile 0 @interp linear operator to our patch will help us to specify non-integer playback rates without distortion.
This example separates 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 sample rate and multiplying it by the number of samples in the buffer.
This is 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 is one more feature of life in the single-sample gen~ world that will be a little bit different - living in a world without the bang message.
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 use 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 have said, "I know just the thing for producing pulses in MSP - the pulse-train generating train~ object!" If so, here is a tip for you: The train~ object doesn't 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 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 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 much simpler. In addition, you don't need to perform 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 - 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 counts 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 a not (!) operator to test whether we will 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 fun....
by Gregory Taylor on
Mar 13, 2018 8:33 PM