Skip to content

Endnotes


[1]Chapter 1: Patching One Sample at a Time

Technically speaking, this isn’t always true: if gen~ can analyze your patch and realize that a signal can be updated less frequently and still produce the same result, it will do so, as one of the many kinds of optimizations it performs to ensure your patch is more efficient. Since the result however is exactly the same as if the signals were always running at audio-rate, it’s clearer to think of them in those terms.

[2] Since gen~ operates primarily with 64-bit floating point numbers, there are hardly any limits on the numeric ranges of signals — the range depends on what they should be used for, and what limits you create by patching. For example, you can set limits of a param operator by defining the @min and @max attributes. Or you can limit any signal’s range using a clip operator (among others).

[3] Note that gen~ offers a few handy operators for converting between standard units, including MIDI note numbers and frequencies (mtof and ftom), samples and milliseconds (mstosamps and sampstoms), amplitude and decibels (atodb and dbtoa), degrees and radians (degrees and radians), and multipliers and decay times (t60 and t60time).

[4] Note that testing equality with signals can be fraught with unexpected behaviour. First, with the resolution of 64-bit floating point numbers, and the temporal resolution of tens of thousands of samples per second, it is highly unlikely that one unknown signal will be precisely equal to another. Even something that is apparently “silent” could in fact just be an imperceptibly quiet signal, and never exactly equal to zero, for example. Testing equality probably only makes sense when working with stepped integer and logic kinds of signals. When working with audio signals, or signals derived from external controls, it usually makes more sense to work with comparisons and thresholds.

[5] If you prefer to use something other than this convention, you can also insert an appropriate logical comparator operator before the inlet, such as > 0 for a greater than zero convention or >= 0.5 for greater than or equal to 0.5. One reason you might choose to do this is when working with audio or control signals that are unlikely to ever be precisely zero.

[6] https://docs.cycling74.com/max8/vignettes/gen_topic and https://docs.cycling74.com/max8/vignettes/gen~_operators

[7] For example, as we will see in Chapter 7, whenever you see a block labeled Z-1 in a filter block diagram, you’re looking at a place where you can use a history operator.

[8] As Heinz von Foerster would say, it is what turns a trivial machine into a non-trivial machine.

[9] For the more mathematically inclined among our readers, this is a function mapping an input domain to an output range. The domain is the set of all values for which a function is defined, and the range of the function is the set of all values that the function produces as output.

[10] Why? The reason is that “branchy” code can often be less efficient, and unpredictably spiky, than non-branchy code. We are often asked whether to use if() statements to select between different processing paths. But it is often better to simply compute both paths, and use a switch or selector operator to choose the result. Modern CPUs can run much faster if they can predict which path of operation is most likely, and using if() or looping branches can confound that prediction. Doing a few extra math operations is much less costly than a prediction miss. Moreover, for() and while()loops can have unpredictable times, which can lead to spiky CPU usage. If you can lay out the operation over a sequence of sample frames instead, you will get more constant CPU performance. This is especially important to avoid audio glitches when running at low latencies, or when exporting gen~ code for embedded computing hardware.

[11] In fact, in its early development, some of the built-in operators that gen~ now provides were originally prototyped and developed in this way.

[12] If you were curious: the title of this book is a mishmash of two reference points whose spirits inspire us. First, there is the term “Organized Sound” attributed to the composer Edgard Varèse almost a century ago as he proposed a music of “sound as living matter” and “musical space as open rather than bounded”. (Arguably, he was describing his own compositions as organized sound largely to side-step questions from the public whether his compositions were music, but it has long since come to be used by many as a definition of music.) Second is the cybernetic and evolutionary sense of “generating and organizing” as used by Brian Eno to contrast a more experimental approach to composition and the arts, with an interest in self-regulating and self-generating processes and riding the dynamics of systems.

Varèse, Edgard, and Chou Wen-Chung. "The liberation of sound." Perspectives of new music 5, no. 1 (1966): 11-19.

Eno, Brian. "Generating and organizing variety in the Arts." Studio International 192, no. 984 (1976): 279-283.

Chapter 2: Modular (Arithmetic of) Time

[13] The gen~ object also includes other operators to handle and convert this rate into other forms: for example, mstosamps will tell you how many sample frames are represented by N milliseconds, and sampstoms will calculate the duration in milliseconds of a number of sample frames.

[14] In mathematical terms, this is also a discrete integrator: the patch is performing integration of the binary signal of the toggle. The word “integration” here is a fancy mathematical term for the process of adding slices to find the whole. Our discrete integrator adds a set amount to the stored value with each sample frame and stores the result. What it integrates is the rate of counting per sample frame. That might not seem very intuitive, but we will see how important this is in Chapter 6.

[15] The switch operator normally has three inlets but adding a 0 as an argument to the operator changes the number of inlets. The switch operator acts like many other operators in gen~ in that adding an argument replaces an inlet. The switch operator is also called the ? operator, so don’t be surprised if you see it under that name in someone else’s patch.

[16] Note that the accum operator’s reset input can behave in two different ways, depending on the setting of the @resetmode attribute. It simply has to do with whether a reset trigger happens before the accumulation, or after. The default setting is accum @resetmode post, which sets the count to zero after adding the left Input. This means that the accum will always output zero while the 2nd inlet is nonzero. In contrast, an accum @resetmode pre will zero the count before adding the left input, so the output will have already started the new count when the reset happens. Again, the point is that the accum operator can perform two actions within a sample frame (count and rewind), and the @resetmode attribute sets what order these happen within the sample frame; "post" will apply the reset after the count, and vice versa. In our history and + version of accum, @resetmode pre means that we route the + to out 1 rather than routing the switch to out 1.

[17] We can also dynamically associate a gen~ buffer mybuf with a different buffer~ play_me by sending the message mybuf play_me to the gen~ object. This technique is really useful because we can have multiple named buffer~ objects in the parent patch and link them into the gen~ object using nothing more than a message.

[18] You might recognize this from the modulo operation, or mod. However, we recommend using wrap rather than mod because of the way that they handle negative inputs. The mod operator in gen~ follows the behavior of modulo as found in most programming languages, which rounds toward zero (known as “truncated modulo”), whereas the wrap operator follows the mathematical behavior in which division rounds toward negative infinity (known as “Euclidean modulo”). The first important difference is that wrap is guaranteed to always output values that are between the low and high limits, even with negative inputs, whereas the mod operator does not have this guarantee. The second important difference is that the direction of movement passing through wrap is always preserved, whereas mod will mirror directions when values pass through zero. For most musical applications the wrap behavior is usually preferred.

[19] By default, the sample operator performs linear interpolation: a method to create a weighted average between samples, as a kind of estimation of where the wave would be, which counters the aliasing effect. We’ll look at this more in Chapter 6.

[20] It is also the tempo-clock equivalent of phase modulation synthesis, which we return to in Chapter 8!

[21] The rate~ object in Max and RNBO, and their replication in gen~ as the rate operator, can also do this kind of clock division. However, it can produce values outside the 0.0 to 1.0 range, in some cases. For this reason, we will use the go.ramp.div abstraction in this book, which always outputs within 0.0 to 1.0.

[22] This pattern can be produced even more simply by multiplying the input phasor ramp by 8/3 and feeding that into a wrap 0 1 operator; but as we will see in the next patch, it can be useful to have the intermediate signals that the multiply, wrap, and divide sequence of operations gives us.

[23] This is a form of multi-operator frequency modulation, but using ramp waves rather than the more usual sine waves. We’ll look at phase and frequency modulation in much more depth in Chapter 8.

Chapter 3: Unit Shaping

[24] Radians are a measure of angle widely used in mathematics and programming. There are pi radians in a semicircle and (two * pi) radians in a circle. So, to convert from degrees to radians, multiply by pi/180 (or use the radians operator), and to convert from radians to degrees, multiply by 180/pi (or use the degrees operator). The use of pi is so common that there are pi, twopi, halfpi, and invpi (1/pi) constants available in gen~.

[25] Compare this shift to the phase shifting of the go.ramp.rotate operator we saw in the previous chapter.

[26] Note that this is not the same as an “equal power” mixer. Even if we apply unit shapers to create nonlinear crossfade curvatures, the mix operator is still applying a weighted average of the two input amplitudes. For an “equal power” mix, which balances energy rather than amplitude (where energy is approximately the square of the amplitude), you can use the go.equalpower abstraction.

[27] The go.unit.arc unit shaper abstraction converts a unipolar ramp into a variety of curves by tracing a circular arc with different distance metrics according to the shape parameter. When the shape parameter is 0.5, a linear ramp goes through unchanged, For shape < 0.5 or shape > 0.5, it traces distance using a power scale that gives convex or concave arc sections. At the limits of 0.0 and 1.0, it becomes square shaped.

[28] Note that some window functions do not quite reach zero—including the Hamming function and the Gaussian and raised cosine under certain parameter combinations.

[29] Here's another interesting difference between filters and waveshapers: While most filters’ responses also depend on their previous inputs or outputs, waveshaping doesn’t. Waveshaping processes are entirely memoryless: the same input will always immediately give the same output, and the function is randomly addressable. This might seem an obscure difference, but there are some applications where it can be beneficial or even essential that a process Is free of history: for example, applying timbral effects to waveform data in a wavetable oscillator, or to Individual grains In a granular synthesizer.

[30] Most input signals are more complex that sinusoids, but if you know what their highest significant frequency content is, then you can compute the highest significant frequency of the waveshaped output would be, by multiplying by the waveshaper's degree. Alternatively, you could use this insight to limit the bandwidth of the signal going into the waveshaper. For example, if you don't want the waveshaper of degree 3 to produce any frequencies above 15kHz, then you could pre-filter the source signal at 15/3=5kHz before waveshaping it. In practice It Is more complex than this, as you'd also want to take Into account the weights of the harmonics to determine how much prefiltering to do.

[31] Interestingly, this endlessly differentiable smoothness is one of the features that has made sigmoids among the commonly used activation functions in artificial neural networks.

Chapter 4: Noise, Uncertainty, and Unpredictability

[32] If this is a subject that interests you, you might find the following to be worthwhile reading:

Maurer, John A. "A brief history of algorithmic composition." Unpublished manuscript. Available at https://ccrma.stanford.edu/~blackrse/algorithm.html (1999).

Loy, Gareth. "Composing with computers: A survey of some compositional formalisms and music programming languages." In Current directions in computer music research, pp. 291-396. 1989.

[33] For almost all purposes, it makes no difference to us whether a signal Is pseudo-random or truly random, as the results are practically indistinguishable. However, given the same initial value (the “seed”), a pseudo-random algorithm will generate the same sequence of random values every time, and that can be a problem. The noise operator in gen~ is seeded according to clock time when a patch is loaded, so this should be different every time; all noise operators share a common generator, which ensures that each distinct noise operator in a patch will generate a different new value. For the curious: gen~ uses the Xoshiro256+ algorithm from Blackman, David, and Sebastiano Vigna. "Scrambled linear pseudorandom number generators." ACM Transactions on Mathematical Software (TOMS) 47, no. 4 (2021): 1-32. (See http://prng.di.unimi.it )

[34] For more detail about these different interpolation types, including the specific underlying algorithms used, take a look at the gen~.interpolation.maxpat patch in the Max Gen examples folder.

[35] Why fold? It acts like a kind of “reflection” off the edge boundaries. We could have used clip, but this would have had the signal sticking to the boundaries too often, and thus would bias the resulting random distribution to those bounds. Using fold maintains a perfectly even distribution while still limiting the values within these bounds.

[36] Named after the Hungarian mathematician George Pólya.

[37] Another way of implementing this would be to shuffle the pack whenever the discard pile becomes the new deck; but we like the pick a card at random method as this performs the shuffle gradually over time rather than all at once.

[38] The go.zerox abstraction simply detects when any Input signal rises from zero or below to above zero, and outputs a single-sample trigger at that point. The use of zero crossing detection in many different applications is explored in Book 2.

[39] It might seem as though bypassing the code using an if block would be more efficient, but this isn’t necessarily the case. With the way that modern CPU architectures work relies on a lot of instruction prediction at the machine-level, which can be confounded by “branchy” code like if, for, while etc. It can often turn out that performing a few extra seemingly unnecessary operations is faster than conditionally bypassing them.

[40] The chaotic equations that we’re demonstrating here and through the patches included with this book can be found among the amazing array of systems on Jürgen Meier’s Homepage at http://www.3d-meier.de. Look under the Tutorials tab — particularly, the links labeled Attractoren I, Attractoren II, and Attractoren III. Jürgen collects information on attractors and provides source code (and the all-important starting x, y, and z and dt parameters for attractors that other references sometimes omit). We all owe him our thanks.

[41]

Chapter 5: Stepping in Time and Space

Of course, pitches are not completely steady for many real instruments, with glides, vibrato, and other curvatures around a steady pitch. The same is true for dynamics, as well. We saw some examples of adding pitch glides Chapter 3, and we look at another way to embellish step functions with fluidities later in the chapter.

[42] Interestingly, this sequence length can be computed using a variation of the Euclidean algorithm, which we will encounter later in this chapter.

[43] See http://www.birthofasynth.com/Scott_Stites/Pages/Klee_Birth.html, http://www.synthpanel.com/modules/cgs13v2_gated_comparator.html, http://www.synthpanel.com/modules/cgs32_infinite_melody.html, https://thehordijkmodular.blogspot.com/search/label/Rungler, https://musicthing.co.uk/pages/turing.html, and https://mutable-instruments.net/modules/marbles/

[44] Incidentally, the technique of applying different weights to delayed copies of a signal is a kind of feedforward filter, often described as convolution with a finite impulse response (FIR). The data we have in the buffer is the impulse response, and a single trigger at the input of the shift register is the finite impulse that evokes this response.

[45] Adding more stages can have an exponential impact on the length of these sequences; for example, a 20-bit shift register can produce a sequence of over a million steps. The noise operator in gen~ uses the Xoshiro256+ algorithm that includes a combination of xor, shift and binary rotation movements of several 32 bit sequences to produce a total period of 2256-1 steps (see http://prng.di.unimi.it). That's more steps than there are atoms on Earth.

[46] At the heart of the octave-to-frequency conversion is an exp2 operator, which is shorthand for raising 2 to a certain power; the inverse operation (from frequency to octave) has at its heart a log2 operator, which tells you what power of 2 the input is. It makes sense really, since rising by one octave means doubling the frequency.

[47] A “normalized frequency” slope representation Is simply frequency/samplerate. This represents how much a unipolar ramp would rise over one sample frame, i.e. what we would need to feed the kind of ramp generator we built back in at the start of Chapter 2. A normalized frequency of 0.1 means a frequency of samplerate/10: it will complete a unipolar ramp every 10 sample frames.

With filters and frequency modulation patches, we may prefer use another kind of normalized frequency representation of “radians-per-sample”, which is simply the unipolar normalized frequency multiplied by twopi (2��). Feeding a radians-per-sample ramp into a sin, cos, or poltocar operator will produce one cycle for every twopi radians.

[48] The period of a periodic waveform in seconds is 1/frequency in Hz, and the frequency in Hz is 1/period in seconds. A period in milliseconds (ms) is 1000 times the period in seconds. A period in sample frames is samplerate/frequency in Hz, and a frequency in Hz is samplerate/period in seconds. To convert between milliseconds and samples, we can use the mstosamps and sampstoms operators.

[49] In fact, the go.unit.lfo unit shaper abstraction is really just a simplified version of this smoothed quantizer, operating over a single unipolar step.

Chapter 6: Filters, Diagrams, and the Balance of Time

[50] Just remember that the “a” input (the 3rd input) should always be between 0.0 and 1.0 if you want the result to stay on the line between X and Y. In patching situations where the “a” value comes from a source whose range you don’t know, you can add a clip 0 1 operator right before the 3rd input to make sure of it.

[51] We already used this design pattern when creating the go.unit.trapezoid abstraction (as subtract duty, multiply, add duty) in Chapter 3, as well as the easing function and asymmetric bipolar shaping templates (in the sequence scale in—<shape>—scale out) and normalized bipolar shapers (as amplify, shape, attenuate). We have also used it when creating the go.ramp2steps ramp quantizer in Chapter 2, for the go.random abstraction, and for pitch quantization in Chapter 5. Those last three examples all use the same approach (multiply by N —floor— divide by N).

[52] T60 is not the only possible measure of a characteristic decay. In analog circuitry one of the classic exponential decays is the release of voltage from a capacitor in an RC (resistor-capacitor) circuit. The decay is often measured by the time in seconds for the voltage to fall by a factor of e-1, which is about 37%. This time, measured in seconds, is called tau, and is related to the cutoff frequency as tau = 1/(twopi*freq). Here is a direct relationship between frequency and characteristic time of decay.

[53] The t60 operator actually implements multiplier = exp(log(0.001)/duration_in_samples), while t60time implements duration_in_samples = log(0.001)/log(multiplier).

Interestingly, if we use a decay level of exp(-1) as the decay level used in RC circuits, and knowing that these have a time constant tau in seconds of 1/(twopi*frequency), we end up with multiplier = exp(-twopi*freq/samplerate), which is precisely the same coefficient calculation we used for the simple lowpass filter earlier!

[54] For a far more accurate emulation (also implemented in gen~), see Parker, Julian, and Stephano D’Angelo. "A digital model of the Buchla lowpass gate." In Proc. Int. Conf. Digital Audio Effects (DAFx-13), Maynooth, Ireland, pp. 278-285. 2013.

[55] A vactrol is a device that combines a light-dependent resistor (LDR) in close proximity with a light source (usually an LED) to process the control signal. As the control signal rises, the light gets brighter, making the resistance of the LDR reduce and thus letting more audio through. Vactrols are interesting because the relationship between the light source and resistance is neither instantaneous nor linear: the more voltage is supplied to the LED, the faster the response of the LDR. Thus, the resistor does not instantly change state when the LED turns on — it takes a little time for the resistance to fall. Similarly, turning off the LED does not change the resistance instantly to full — it takes some time to decay.

[56] We noted already that the lowpass one-pole is a kind of “leaky” integrator, and conversely, the highpass is a kind of leaky differentiator, in that it reveals more about the slope of a signal than its position. A constant input has no slope and zero output from the highpass; a noisy input will reveal the slopes of the jumps up and down. This shouldn’t be a surprise: we are taking a weighted average between the original signal, and the differentiated (x[n] - x[n-1]) slope of that signal. The coefficient sets up to what degree we are balancing the signal against its own changes!

[57] For a freely available online example, look at the W3C Audio EQ Cookbook, adapted from an article by Robert Bristow-Johnson: https://www.w3.org/TR/audio-eq-cookbook

[58] For example, among the lowpass, highpass, bandpass, resonant bandpass, notch, and allpass designs, the computation of the a0, a1, and a2 coefficients are all exactly the same. All of these filter types convert the desired cutoff frequency into the equivalent radians per sample and use both the sine and cosine of this value. Similarly, all of these filter types compute a value called alpha and the Q factor in exactly the same way. In fact, the only significant differences between the filter types are how the b0, b1, and b2 coefficients are computed from alpha and the cosine of the radians per sample.

[59] This patch is effectively similar to the filtercoeff~ external in Max and RNBO.

[60] The discrete implementation of the bilinear transform maps an infinite possible frequency range to a discrete range up to samplerate/4, which thus warps higher frequencies downward. Instead, the coefficient calculator limits our frequency range up to samplerate/4 and applies a tan-derived function curve to "pre-warp" the frequency and adequately compensate for the warping. (For a more explicit explanation, see https://www.native-instruments.com/fileadmin/ni_media/downloads/pdf/VAFilterDesign_2.1.0.pdf#page=69)

A side-effect of this is that the trapezoidal filters will tend to attenuate frequencies toward zero as they approach the Nyquist limit (samplerate/2). That might itself be desirable, but if not it can be circumvented by oversampling the filter, as we shall see in Book 2.

[61] For an example, see Parker, Julian, and Stephano D’Angelo. "A digital model of the Buchla lowpass-gate." In Proc. Int. Conf. Digital Audio Effects (DAFx-13), Maynooth, Ireland, pp. 278-285. 2013. A further discussion of resolving “impractical” block diagrams can be found at https://www.dsprelated.com/showarticle/990.php

[62] One of the perennial questions about gen~ that has come up on user forums is “How do I make the equivalent of Max’s line~?” This section shows how!

Chapter 7: The Effects of Delay

[63] Note that throughout this chapter we use the delay operator to process audio signals, but it can of course be used to process any kind of signal, not just audio.

[64] This capability of the delay operator is enabled by default, but it can be disabled by setting @feedback 0 on the delay. Why would you want to do this? With feedback enabled, the delay operator cannot produce a delay shorter than 1 sample in length since at least one sample of delay is needed to allow feedback (just like with history). With feedback disabled, the delay time can go right down to zero samples. Practically, the difference is whether the reads happen before or after the writes. If you look at the code view, you’ll see the corresponding delay.read() and delay.write() instructions change order with @feedback 1 and @feedback 0.

[65] For more on this subject, see Dattorro, Jon. "Effect design, part 2: Delay line modulation and chorus." Journal of the Audio engineering Society 45, no. 10 (1997): 764-788.

[66] Hyun Ahn, Jae, and Richard Dudas. Musical Applications of Nested Comb Filters for Inharmonic Resonator Effects. In Proceedings of the 2013 International Computer Music Conference. Ann Arbor, MI: Michigan Publishing, University of Michigan Library, 2013.

[67] Incidently, this diagram is an example of a nested filter. All that means is, one or more elements within a diagram is replaced by another block diagram, thus nesting one filter response within another. If you look closely, you might recognize the inner structure of this diagram (with the -k and k coefficients) is practically the same as the allpass filter we saw in the previous chapter, but with the Z-1 history operation replaced by a Z-M delay operation. We'll see more allpass delay structures in Book 2.

[68] Karplus, Kevin; Strong, Alex (1983). "Digital Synthesis of Plucked String and Drum Timbres". Computer Music Journal. MIT Press. 7 (2): 43–55. doi:10.2307/3680062. JSTOR 3680062.

[69] Note that the highpass DC blocking filter we added earlier does not add any appreciable delay for the same reasons we explored in the highpass filter section of Chapter 7.

[70] You might enjoy the detuning effect as something a little more organic, and if so, it can liberate some quite enjoyable explorations into inserting multiple filters and filter types into the delay loop. A mix of a lowpass and several bandpass filters can produce some quite lovely multiphonic effects. If you are using resonant filters, you may also want to incorporate feedback limiting (saturation) as we did earlier in this chapter.

[71] Briefly, linear interpolation is a weighted average, and averaging tends to flatten out the fastest changes in a signal, which means losing information in the higher frequencies. The more our desired delay Is estimated between samples, the more we are relying on estimated rather than actual data, and thus the more the result loses higher frequency energy. This is why some pitches sound brighter than others. Unfortunately, switching to another built-in delay operator interpolation mode such as @interp cosine or @interp cubic doesn’t help very much here; all of them have some kind of averaging effect.

[72] Many of the filter diagrams that we have looked at combine both feedforward and feedback paths, such as the allpass and biquad filters; the presence of a feedback loop is likely to give these an infinite impulse response (IIR), unless the coefficients effectively cancel the feedback. The closest things to FIR filters we have seen so far in this book are the shift register based smooth random generator in Chapter 4 and the weighted shift register sequencer in Chapter 5.

Chapter 8: Frequent Modulations

[73] Scaling isn’t the only way to make a bipolar signal unipolar. For example, we could send it through a max 0 operator (this is also known as half-wave rectification) or an abs operator (which, in turn, corresponds to full-wave rectification) or a fold 0 1 (a kind of wave-folding) etc. However, each of these operations will change the shape of the modulator waveform, and likely introduce a lot of aliasing harmonics. Scaling the waveform is the way to make it unipolar while preserving its original shape.

[74] The point of transition between events, rhythms, and timbre is particularly interesting, in that it does this at almost the same frequencies that can turn a series of still images into the perception of continuous motion in animation (the phi phenomenon at typically 20-30 frames per second).

[75] The use of the word ring in the name of this technique comes from the physical layout of diodes in a ring structure in the analog circuits that historically implemented this kind of modulation.

[76] According to Bill Schottstaedt (per https://ccrma.stanford.edu/software/snd/snd/fm.html), this is exactly how John Chowning accidentally discovered the technique, which was then developed by Yamaha in the renowned DX7 and other synthesizers. Unfortunately, it was marketed with the misnomer of frequency modulation (FM) when in fact these synthesizers implemented phase modulation. Since then many other synthesizers implementing phase modulation also called it “FM”.

[77] Considering the phasor and * twopi carrier subcircuit as an angular integrator, we could think of both FM and PM as two kinds of “angular modulation”, with the difference being whether the modulation is added into the input of the integrator (FM), or to its output (PM).

[78] Mathematically, the sideband intensities depend on Bessel functions, which are not very intuitive and are beyond what we cover in this book.

[79] https://cycling74.com/forums/carrier-and-harmonicity-in-fm-synthesis

[80] Actually, a sine wave shifted by a quarter turn phase difference, but we can’t hear that difference!

[81] United States Patent US4249447A “Tone Production Method for an Electronic Musical Instrument” Norio Tomisawa, 1979 (Expired). Currently assigned to Yamaha Corp Nippon Gakki Co Ltd.

[82] Filters to block DC (“direct current”) are sometimes called AC couplers (“alternating current”); both terms being inherited from equivalents in analog electronic circuits.

[83] Here is yet another intriguing connection with integration. In Chapter 6, we saw that a lowpass filter is a “leaky” integrator, and a highpass filter is the opposite operation (a kind of leaky differentiator). Earlier in this chapter, we pointed out how with FM, the modulation is integrated by the phasor operator (and vice versa). So perhaps it makes sense that correction to PM also requires the integration of a lowpass filter, whereas correction to FM requires the opposite operation!

[84] The issue is also present in FM, but is less noticeable, since a sudden jump of magnitude in the modulator leads to a sudden change of slope in the carrier. A change of slope isn’t as noticeable as a skip in phase, but it may still add a triangle wave-like sonority if the modulation is periodic.

[85] This also works with FM, with the difference noted earlier that the modulator signal is integrated before being waveshaped; so we hear the effect of the rate of change of the modulator instead.

[86] Through-Zero FM (TZFM) simply means that the analog oscillator has additional circuitry to be able to work with negative frequencies without running into difficulties. In contrast, for digital oscillators, negative frequencies pose no problems, and almost all of the FM patches in this chapter are linear TZFM.

[87] This idea was presented in J. A. Moorer, “Signal Processing Aspects of Computer Music: A Survey”, Proceedings of the IEEE, 65 (8), pp.1108 - 1141 1977

[88] Carson, John R. "Notes on the theory of modulation." Proceedings of the Institute of Radio Engineers 10.1 (1922): 57-64.

[89] As you might imagine, the same principle of reading ahead / reading behind can be applied to reading audio from a buffer or data operator, and the same principles of PM and FM apply there too.

[90] However, for a more robust phase-modulated string model, the example at the end of Chapter 8 is recommended.

[91] Timoney, Joe, Victor Lazzarini, and Tom Lysaght. "A modified FM synthesis approach to bandlimited signal generation." Proceedings of the 11th International Conference on Digital Audio Effects (DAFx-08), Espoo, Finland. 2008.

Chapter 9: Navigating Waves of Data

[92] We recommended the wave operator here not just because it has these convenient sub-range inputs, but because it will also perform interpolation properly when wrapping over the edges of the sub-range. For example, if you set the range to be between 1000 and 1999, and at some point your phasor ramp is asking for the interpolated value at 1999.5, then it will output a blend between samples 1999 and 1000 (whereas the sample operator would have given you the blend between samples 1999 and 2000 – which is outside the sub-range!)

[93] The word voxel is a shortening of the term “volume element,” similar to how the word “pixel” is a shortening of the term “picture element”.

[94] If you’re wondering why there are so many harmonics, it’s because the sharp transition in a saw wave has theoretically infinite curvature, which produces an infinite series of harmonic frequencies. These are the frequencies that we cannot actually represent digitally, and what we need to filter away once they are above a reasonable range of hearing.

[95] Factum: The letters MIP in the name MipMap are an acronym of the Latin phrase multum in parvo, which means "much in little".

[96] Image courtesy of Wikimedia commons, at https://commons.wikimedia.org/wiki/File:Mipmap_Aliasing_Comparison.png#/media/File:Mipmap_Aliasing_Comparison.png, CC0 1.0 Universal (CC0 1.0) Public Domain Dedication

[97] If you wanted to save more computation, you could pre-bake the sinc filtering into a series of wavetables, one for each octave. This would make the playback patch a little more complex, but only slightly (it is analogous to what we did with the 2D wavetable set). However it also sacrifices the ability to modify the wavetable data dynamically.

[98] This relationship between effective resolution and harmonic limits might remind you of the mipmapping method we explored earlier in this chapter. It might be worth investigating terrains as an alternative way to help band limit wavetables.

[99] This technique of using regular polygons to generate audio output is documented in the paper “Continuous Order Polygonal Waveform Synthesis” by Christoph Hohnerlein, Maximilian Rest, and Julius O. Smith, in the Proceedings of the International Computer Music Conference, University of Michigan Library, 2016. It is also the basis of the Polygogo Eurorack module.

[100] If you’re feeling ambitious, perhaps you might think about how to extend orbit generators to 3D, to drive a 3D wavetable oscillator for example – or even thinking about what a 3D terrain (and synthesizing trajectories through a voxel field) would work.

[101]

Chapter 10: Windows of Time

Curtis Roads accounts for the use of “Pulsar” in this nomenclature in reference to the pulsars of astronomy – spinning neutron stars that emit periodic signals in the range of 0.25Hz to 640Hz, coincidentally a range of frequencies between rhythm and tone of central importance to pulsar synthesis. For an in depth exploration of microsonic techniques, including pulsar synthesis, see Roads, Curtis. Microsound. The MIT Press, 2004.

[102] If you wanted to set the pulsaret grain duration as a duty cycle between zero and one, rather than a division ratio of 1.0 or greater, all you need to do is take the reciprocal of the duty (using a !/ 1 operator) to get the equivalent division ratio.

[103] This is analogous to Heisenberg uncertainty principle of particle physics: the more sharply we can position the event in time (due to a shorter window), the more uncertain or diffuse becomes its frequency (a wider spectrum), and vice versa, the more certainty we have over the spectral shape, the longer the window we need.

Remember also that windowing is a form of amplitude modulation (AM), and as we saw in Chapter 8, amplitude modulation introduces sidebands that depend on the modulator’s frequency. Shortening the window duration will increase its frequency content, causing the sidebands to spread further apart. Moreover, the less smooth the window shape (and the more complex the carrier), the more sidebands will be produced.

[104] These and many other inspiring ideas can be found in Xenakis, Iannis. Formalized music: thought and mathematics in composition. No. 6. Pendragon Press, 1992.

[105] If this isn’t clear, perhaps it helps to look at it the other way around. Imagine we have a sample counter that loops counting by ones from zero to ten. Since it counts by ones, the slope is 1. To get a phasor out of this we’d need to divide by the loop length (10). Then the slope would be 1/10 per sample. So, to get a sample counter back from a phasor we do the opposite: dividing by 1/10 is multiplying by 10, getting back to our original sample counter.

[106] The processing overhead is constant, regardless how many notes are actually playing, but this could be an advantage in some situations. For example, if we are designing an algorithm to export to an embedded hardware platform, a predictable computational cost can be preferable to an unpredictable one, as it is the maximum computational cost that determines whether the audio algorithm is viable or not.

[107] The fourth inlet to a poke operator sets the amount of the existing sound in a data or buffer to retain. By default this value is zero, which means the poke operator will by default overwrite and replace any existing sound. Setting it to 1.0 will preserve all of the existing sound and add the new sound to it. You can also think of it as a feedback gain control, if the data is being used as a delay.

[108] Another catch is that this algorithm only really works with integer delay times, since a poke operator can only write into one sample slot of the tape at once – it cannot interpolate. Since we can’t smoothly modulate delay times this is perhaps less of a problem than it might seem.

[109] The method described here is different from but inspired by a method using box integrals in “Anti Aliasing Oscillators and Distortions with Pre-Integrated Wave Tables”, Thierry Rochebois, 2016. The method described here avoids the complexity of the integrals themselves but reproduces equivalent waveforms.

[110] Of course, the physical reality isn’t a straight line either (e.g. it can’t sharply change directions), and depends on many factors, but this approximation is a lot closer than a step function, and easier to understand.

[111] We saw in Chapter 9 that using sinc interpolation can also suppress aliasing on general waveforms, and will see more methods in Book 2 that can do more precise antialiasing for specific waveforms. However, these benefits come at a price of greater computational complexity, and also latency in some cases