The best thing to do if we want to capture a realistic sample of swords striking each other is to record two swords striking together. I’ve always wanted a sword, but my wife has never let me have one, let alone two. But really, what’s a sword but a relatively thin bar of metal? An alternative could be to record a sample of two metallic things striking together and manipulate it as needed to produce a swordlike sound.
In the code download, alongside the wineglass sample, in the file named sound/single-knife-hit.wav is a sample I recorded of two kitchen knives striking together. We can load this sample into a table when Pd loads the patch, and then play it back at a lower pitch to make the knives sound more like swords striking.
To do this, we’ll just play back the sample at a slower speed. This technique is the simplest approach we can take to change the pitch of the sample from a high-pitched knife sound to a lower-pitched sword sound. If we play the sample at half the speed, every frequency in the sample sounds twice as low.
This works for our purposes because small physical bodies, such as knives, vibrate faster when struck than larger ones, such as swords. There are more complex ways of changing a sample’s pitch to make the pitch change while keeping the length the same or to keep pitch the same while changing the time, but in this case I think it sounds better for our purposes to use the simpler technique.
We’re also going to have a way to control the sound’s duration so that we can have shorter and longer strikes. This is a way to add a little variation, and could be tied to a game variable. We also want to have a way for the strike to sound like it happened on the right or left side of the stereo field—the panning, which could also be tied to a game variable.
The Patch
The patch will load the knife-striking sample and present a few test messages to test out different pitches and durations. Instead of having a variable to directly control the panning, for this test patch we’ll randomly pan the sound somewhere in the field to get the feel for what it might be like in the game.
The Main Patch
Create a new patch and save it somewhere handy. Copy the sound/single-knife-hit.wav sample into the same directory, or wherever you’d like it to be. Next create an array named “clang,” a loadbang, a soundfiler, and a message to read the sample into the array—for example, read -resize ../../sounds/single-knife-hit.wav clang. Adjust the message for the relative path to your sample. You will have a patch that looks something like the following image.
soundfiler
read -resize ../../sound/single-knife-hit.wav clang loadbang
s samples $1 clang
Below the soundfiler is a send object, s samples $1. When soundfiler is finished loading a file, it sends to its bottom outlet the number of samples read from the file. We capture this in the send object’s dollar-sign variable, and send it as a message called samples. The number of samples read into the array is important information we’ll use to play back the array’s contents.
Next we’ll create a set of test message boxes for different values that we’ll use to control the playback. The variables we’ll use for the control parameters will be as follows:
• pitch will control how fast we play the sample, with a value of 1 being normal speed.
• envelope will trigger an envelope to play the sample.
• duration will control how long we want the sample to play.
In the patch from the sample code, as the following figure shows, there are four message boxes for a few different examples.
soundfiler
One plays the sample as is, as a reference, and then three more play it at around twice as slow, with slightly different durations and variations in speed.
Create those messages and then the r objects for each control variable as in the preceding image. The patch so far works as sort of a control layer, and pushes the mechanics of playing the sample into a subwindow abstraction.
Create that subwindow now with an object containing pd sword. In the messages before we send envelope 1 we first send envelope 0 to stop any previous sound.
Controlling the Sample Playback
Inside this subwindow we’ll create the objects to play the sample back at the speed that we want, as well as control the duration of the playback. First let’s revisit what it takes to play a digital sample.
We’ve loaded the contents of a .wav into a Pd array. That file was recorded at a sample rate of 44100. Remember that the sample rate is an indication of how many samples were taken every second. The number of samples in the file was calculated by the soundfiler object and broadcast as a message called samples, allowing us to capture that number for use in our calculations.
If we want to play back the samples from the file at the same speed it was recorded, we need to send 44,100 samples to Pd’s output every second. To do that we need to know the number of samples in the array and how long the original sound was. Since we know the number of samples and the fact that the sound file was recorded at a sample rate of 44100, we can divide the samples by 44,100 to determine the length in seconds. Then, if we want to change the speed at which we play the sample back, we can vary the length by a factor to adjust the playback speed. The contents of the subwindow in Figure 17, Varying the Length of the Playback, on page 88 do just that.
First of all, there are three inlets corresponding to the three control parame-ters: envelope, pitch, and duration, with the first two connected to an
‘adsr~‘ envelope. Those inlets will receive input when the message boxes are pressed. The r samples objects will receive input as soon as the sample file is loaded, though, and the left receive object will store the number of samples into the adsr~ inlet that controls the attack initial value. The right receive object divides the number of samples by 44.1 and stores it in a *~ object, where it will scale the incoming pitch value.
It’s not strictly necessary that this patch use an adsr~ envelope; we could have used a line~ object and a set of other objects to control it, but the adsr~ takes up a little less space, and it is really a set of line~ objects inside the subpatch anyway.
tabread4~ clang
line~
1
*~
0 $1
t f b
pd randopan
outlet~ left outlet~ right inlet envelope inlet pitch
inlet duration r samples
*
r samples / 44.1
into length Convert samples
../BuildingControls/adsr~ 0 0 0 0 0
Figure 17—Varying the Length of the Playback
The goal of the top half of this subwindow is to control the tabread4~ in the middle. A tabread4~ reads from an array, which contains a table of data. It uses four-point interpolation, which basically means that it knows how to wrap around seamlessly to the beginning when it reaches the end of the table.
Here it will read from the “clang” table, but it needs to be told how fast to read. We do that by using the envelope to tell it to read from the array sample by sample. The envelope will be initialized with its attack value set to the number of samples in the file by the r samples receiver.
Then, when it receives a 1 message sent into the inlet envelope, it will ramp down to 0 over a certain number of milliseconds. That number of milliseconds
will be based on the number coming into the inlet pitch. We want the pitch control variable to be 1 for a normal playback speed. The number calculated and stored in the * object will be the percent of the speed at which we want the file to be played, so if the pitch message is something other than 1 it will scale the playback time accordingly, making the adsr~ ramp smoothly from the number of samples down to 0 in that amount of time. As the envelope ramps, it tells tabread4 which sample to read from the array.
The inlet duration will get the duration we want the sound to play and use a trigger to send a message to a line~. First it initializes the line~ to 1 by sending 1 to its right inlet, and then it sends a message to ramp down to 0 in the number of seconds specified to the duration inlet. This doesn’t do anything fancy to the sample playback by tabread4~; it simply fades it out because it’s being multiplied by the *~.
Finally, the signal passes through the randopan abstraction, which we’ll talk about in a second, and gets sent to two separate left and right outlets. Create all the objects described previously using the images as reference, and then create a subwindow with pd randopan, and we’ll see how we have Pd randomly pan the sword-strike sound between the stereo channels.
Random Pan Location
Panning is the process of placing a sound in a stereo field. As you probably know, stereo sound is produced from a right and left speaker, and helps us perceive sound as naturally coming from a certain direction. Pd produces stereo and provides us with a right and left inlet on objects such as dac~ and output~. When a sound is more present, or louder, in the right channel, we perceive it as coming from the right. The final subwindow in the patch is the random panning feature. This will randomly pan each sword strike it receives between the right and left channels to simulate giving some spatial depth to the sound the user could correlate to visuals onscreen.
Inside the subwindow create an inlet signal object and two outlet objects, called left and right. The subwindow splits a signal into two, with a randomly calcu-lated amplitude for each, such that when added together they equal 1. Create the rest of the objects you see in Figure 18, Varying the Length of the Playback, on page 90, and we’ll discuss what roles they play.
Each time the envelope message is received, we calculate a new random number between 0 and 100 with the random 100 object. Then we divide that by 100 and send the value to the left outlet. Before we send the value to the right outlet we calculate the inverse with the swap 1 and the subtraction object (-).
This is the technique that we saw before in the low-frequency oscillator (LFO)
inlet~ signal
outlet~ left outlet~ right
*~ *~
random 100 / 100 r envelope t b
- swap 1
sqrt sqrt
Figure 18—Varying the Length of the Playback
subpatch. The signal is multiplied by these numbers and sent to the outlets.
Now the left and right outlets will have some portion of the signal passing to them, randomly balancing the full signal between the two.
Before mixing in the sides together with each other using a *~, we take the square root of each side with sqrt~. This is a better approach than simply splitting the signal in two, because if we did that, then the sound would actually be half as loud when panned to the center. This may be counterintu-itive, but it has to do with the fact that the actual loudness is the square of the amplitude. Mixing in the square root of the other side’s signal leaves more amplitude when panned near the center, and addresses this issue better.
This is one strategy for building a panner. For a more in-depth look at the theory and a few different strategies for building controls like panners, have a look at Chapter 14 in Designing Sound [Far12].
Wrap-Up
This patch nicely creates the sound of two swords clanging together with a fairly simple technique, and gives you an example of how to get much more control over a static sample.
Now let’s look at a more complex, even more interesting patch that models an iconic sound that should be instantly recognizable: a lightsaber.