f04. FTM Basics — Buffer for MSP


Making a Matrix Accessible to MSP


So far, although we've been dealing with matrix content that is relevant to sound and music, we haven't listened to it via MSP. In the previous chapter it was demonstrated that a fmat might contain one or more waveforms, and that you can import a soundfile into a fmat. A fmat might also include a window or envelope for shaping sound, or an FFT analysis of a sound, or other control or analysis data for audio/music purposes.


To access that data as sound in MSP, there is a whole family of FTM objects called the Gabor collection, which we will delve into in the following chapters. For now, this tutorial just demonstrates how to make a matrix accessible in a MSP buffer.


Displaying and Using Sound Data in a fmat


This patch allows you to view a waveform and its spectrum, and to hear that waveform used as a continuous tone.


The patch contains four fmats, each consisting of a 512x1 matrix pre-loaded with a single cycle of a waveform. The waveforms are a sawtooth wave, a square wave, a pulse train, and an electric guitar. The first three are band-limited imitations of the true canonical waveforms, each containing only the first eight partials of that waveform. They therefore sound (and look) low-pass-filtered relative to the true waveforms, but they are less likely to create aliasing when used as the fundamental tone of a high note. The guitar waveform was extracted from a famous chord played by a famous rock guitarist (thanks, Pete), and is left unfiltered for all its original nastiness.


• Choose the different waveforms from the popup menu at the top of the patch. The display on the left shows the contents of the matrix (in the time domain, as it were) and the display on the right shows it spectrum (amplitudes of its components in the frequency domain).


Because each fmat is a 1D matrix, the ftm.vecdisplay on the left draws it with no trouble, just as if it were a fvec. (The $1 ftm.mess is just for patch cord management; it passes the matrices through unchanged.)


To convert that waveform into its spectrum representation, the following steps are taken.


First, in order to leave the original fmats undisturbed by the methods that are about to take place, we make a copy of the matrix with the ftm.copy fmat object. This is equivalent to ((new fmat) set $1). It dynamically creates a copy and refers to that copy instead of to the original matrix in memory (and then frees up the memory when the copy is no longer needed).


The fft method converts the matrix copy into a 2-column matrix containing the real and imaginary values of the calculated FFT and returns the result.


[Technical but important details: The size of the FFT performed by fmat's fft method is determined by the number of rows in the fmat. The FFT is calculated using a size that is the smallest power of 2 that's greater than or equal to the number of rows in the fmat. If the number of rows in the fmat is not a power of 2, fft will supply and zero-pad extra elements of the matrix up to the next power of 2. (For example, if the fmat has 513 rows, the FFT will be calculated using 1024 points, with the last 511 points set to 0.) In this particular case, the matrix has 512 elements in it, which is exactly a power of 2, so the FFT is calculated using 512 points. Also... Because for audio signals the magnitudes of the negative frequencies (in bins 257-511 of the FFT in this case) are the same as the positive frequencies (in bins 1-255 of the FFT), fft actually only returns bins 0-256 (257 bins). In all cases, the number of rows in the matrix returned by the fft method will be equal to the FFT size divided by 2 plus 1 (in this case, 512/2+1=257).]


The cabs method then calculates the absolute values of those complex numbers (i.e. the magnitudes) and returns them as a 1-column matrix. It's important to understand that the FFT provides those magnitudes scaled by the number of points in the FFT so to get the true magnitude one should divide those values by the number of points in the FFT. And since the fft method only returns the positive frequency bins, we should multiply the magnitude values by 2. So, what we really want to do is divide all the magnitudes by the one half the size of the FFT, which can be found by taking the rows of the matrix minus 1. (Whew!) So, in the ftm.mess to the right of that, we use the rows method to find its length, we subtract 1 from that, and we send the result to the right inlet of another ftm.mess to scale the magnitudes back down to the correct value between 0 and 1.


[For the overly efficiency-minded: Since the programmer knew that the length of the original fmat containing the waveform is 512, why not just put 256 in place of the $2 argument? That's true of course, but this way is more general and works for any size of fmat, and it also provided an opportunity to introduce you to the rows attribute of fmat.]


Note that all of these methods we've just discussed—fft, cabs, and div—are in-place operations. They replace the contents of the matrix with the result of their calculation. That's why we used a copy of the waveform matrix rather than the original.


Finally, we want to view the spectrum but we know that (at least in the case of the first three waveforms) there are no harmonics present greater than the 15th harmonic in the case of the square wave, or the 8th harmonic in the case of the sawtooth wave and the pulse train. So we use the colref method to refer to only the first 16 elements (0-15) of the spectrum for viewing. (The guitar waveform has more harmonics than that, of course, but they are low in amplitude relative to the fundamental.)


ftm.buffer


FTM provides an object called ftm.buffer that makes a fmat available for use as a MSP buffer. You can think of it as the FTM equivalent of the MSP buffer~ object because it can be accessed by all the MSP objects that normally access a buffer~ such as cycle~, index~, lookup~, wave~, play~, groove~, waveform~, etc. It actually creates an invisible (globally available) named buffer~, and refers to a fmat for its contents.


The four ftm.buffer objects in the lower right corner of the patch demonstrate the syntax; the arguments are the name of the buffer and a reference to the matrix that the buffer will use for its contents. Note that it's a good idea to use different names for the buffer and the matrix it refers to, just to avoid confusion. (The fmat names are part of FTM's privately managed name space, and by default are local to just the patch they're in, whereas the buffer names are part of Max/MSP's global name space. Nevertheless, it's good programming practice not to create opportunities for confusion any more than is necessary.)


When the user chooses a waveform from the popup menu, it the menu item number calls up the corresponding ftm.buffer name in another menu that is set to Label mode, which shows the chosen buffer name and sends it to the cycle~ object as the argument of a set message. That causes cycle~ to refer to that buffer for its waveform. The menu item number also goes to a select object, which triggers the proper fmat name reference for the visual displays.


Summary


Within a fmat you can store a waveform or sound clip, do an FFT analysis of it—which returns a 2-column matrix reporting the nonnegative frequency half of the FFT—and get needed information about the sound. Such operations generally are done in-place, so if you don't want to overwrite the original matrix you should work with a copy of the matrix. The fmt.copy fmat object returns a dynamically created copy of the matrix it receives.


Any fmat can be used as a buffer~ just by referring to it with a ftm.buffer object. The Gabor objects provide a much broader range of capabilities for using FTM data structures in MSP, and those objects will be the subject of the next group of tutorials.