STRUM
STRUM generates plucked-string sounds based on a neat hack developed by Kevin Karplus and Alex Strong.
The k-s plucked-string algorithm is a subtractive synthesis system featuring a burst of white noise, a recirculating delay line, a lowpass filter, an allpass filter, and some serious math involving things like the letter sigma which i won't talk about here (see Roads, 1997).
The basic idea is that a burst of noise is pushed through a delay line, which splits its output, sending one half as output and the rest of it back into itself after going through a lowpass and allpass filter setup. The result is a burst of rich sound that gradually loses its higher harmonics as it decays (as does, funnily enough, a plucked string). A number of simple additions to the instrument design have fine-tuned the algorithm so that you can get some pretty realistic (or decidedly weird) effects along the lines of amp-driven guitars or tremelo-picked mandolins.
STRUM, like
METAFLUTE,
has a few sub-instruments inside of it. The basic instrument,
START(), is your generic dry plucked string synthesis. When placed after START() commands, BEND() adds string bending, and FRET() emulates, er, two-hand tapping (?). START1() and its friends, BEND1(), FRET1(), add amplifier distortion, and VSTART1() and VFRET1(), in turn, add vibrato to that.
Syntax:
The syntax for strum is a bit of a mess, but here goes:
NOTE: gen slot 1 (makegen(1, 24, 1000, ...)) is reserved for an amplitude envelope, so you can get that
nifty "fade-in" guitar sound, or get rid of clicks at the end of the sound.
regular plucked strings (sounding most like mandolin or guitar:
START(outskip, duration, pitch, funddecay, nyquistdecay, amplitude, squish, stereo-pan, deleteflag)
A couple of notes on some of these:
- Specifying duration: This involves 3 of the parameters: duration, nyquistdecay, and funddecay (fundamental decay.) These can be explained most simply by thinking about a plucked string's behavior. After you pluck it, the sound decays. The higher partials decay, first, and on downward, until, finally, the fundamental frequency decays to 0. You can specify exactly how this happens with the nyquist-decay and fundamental-decay parameters. These are specified in seconds. (Remember that nyquist is the highest possible frequency for a given sampling rate, in our case 22050 Hz.) Thus nyquist-decay controls the decay rate of the highest, fastest-decaying partials of the sound. Fundamental-decay, then, controls the decay rate of the lowest, slowest-decaying partial. Now, here's the tricky part: if you want a given pluck to ring out for its full decay-time, you just make sure that funddecay and duration are equal. Consider that to be a default setting. If you want to "cut-short" the decay-time of a pluck, then you can make duration shorter than the funddecay. Things get even more complicated with BEND and FRET, but I'll [try to] explain below.
- squish: This parameter tells how "squishy" is the item being used to pluck the string. Values generally range from .000001 to 10, though any can be tried, you never know what might happen. Generally, the lower the value, the harder the plucking object. The higher, the more "fleshy."
- deleteflag: the last optional p-field for START, (and START1, and VSTART1):
Generally you can safely let this default to "0", or leave it out. However,
the STRUM instrument allocates memory for some of the delay lines it uses,
and if you generate huge numbers of notes from an interactive application
you can cause a serious memory drain on the poor computer. Setting this
optional p-field to "1" will tell STRUM to free this memory after every
note, and will allow you to create billions and billions of notes. A
setting of "1" will disable any future FRETs or BENDs on that particular
note, though... because the memory is freed, FRET or BEND will not have
any sound to work with.
BEND(start, duration, pitch0, pitch1, glissfunc, funddecay, nyquistdecay, updatefreq, stereo_position) /* important note -- will only work after a START() command */
Now for the parameters with "issues" in BEND:
- pitch0, pitch1, glissfunc: Glissfunc is the slot number of a makegen that you create. That makegen should be a curve describing the way you want the bending to occur. The curve is, of course, made of time-value pairs. A "value" of 0 in the curve corresponds to pitch0 in the BEND comman. A "value" of 1 corresponds to pitch1. Thus, assuming you have specified the following makegen:
makegen(8, 24, 10000, 0,0, 1,1, 2,.2, 3,.8, 4,.5, 7,.5, 9,1, 13, 1)
The instrument will interpret this as: "Begin at pitch0, go to pitch1, go almost to pitch0, go almost to pitch1, go right in between them, hang out there for a while, then go to pitch1 and hang out there 'till the end." (Note that since this makegen is in slot 8, you would assign a value of 8 to the glissfunc parameter.)
Which pitch is higher makes no difference. It only matters which comes first and second in your BEND command. A curve going from 0 to 1 thus could go up, or it could go down, depending on pitch0 and pitch1. This means that normally, if your gliss-curve starts at 0, then pitch0 should be the same as the last pitch specified (either in a preceding START command, or the last pitch reached in a BEND command (You can string BENDs together to get a kind of sitar-like articulation.)) If it's not, the sudden jump is going to cause a lot of noise (which you may find useful sometimes.)
- Duration issues, again: Remember that a BEND must come after a START. Therefore, the "duration" at issue here is the duration of the original START note. Any BEND stuff must happen within the specified duration of the original START. Normally, the following table can give you the appropriate values for the durational parameters for a given BEND command, in relation to its "parent" START command:
- BENDstart= somewhere within the duration of the original START.
- BENDdur= STARTdur - (BENDstart - STARTstart)
- BENDfunddecay= BENDdur
- BENDnyqdecay= STARTnyqdecay - (BENDstart - STARTstart)
If you want to have a string of BENDs, well . . . . you get the idea. Remember, it's a physical model of a plucked string that decays, so, figure it out logically from that perspective.
- updatefreq: This is a parameter telling how often to "update" the frequency of the string as it's glissing. Generally values like 2000 seem to work pretty well (if it's too small, you get clicking and other nastiness.)
FRET(start, duration, pitch, funddecay, nyquistdecay, spread) /* the pitch field is the pitch that alternates with the one specified in the preceding START() command */
Similar duration constraints to those listed above apply to this as well. It's a bit simpler, though, as there are no gliss-curves to deal with.
guitars with feedback:
START1(outskip, duration, pitch, funddecay, nyquistdecay, distortiongain, feedbackgain, feedbackpitch, cleanlevel, distortionlevel, amplitude, squish, spread, deleteflag)
BEND1(start, duration, pitch0, pitch1, glissfunc, funddecay, nyquistdecay, distortiongain, feedbackgain, feedbackpitch, cleanlevel, distortionlevel, amplitude, updatefreq, spread)
FRET1(start, duration, pitch, funddecay, nyquistdecay, distortiongain, feedbackgain, feedbackpitch, cleanlevel, distortionlevel, amplitude, spread)
guitars with vibrato and feedback:
for VSTART1 and VFRET1 only:
makegen(2, 10, 1000, p0, p1...) /* waveform for the vibrato */
makegen(3, 24, 1000, t0, a0, t1, a1...) /* envelope for the vibrato, in time/amplitude pairs */
VSTART1(outskip, duration, pitch, funddecay, nyquistdecay, distortiongain, feedbackgain, feedbackpitch, cleanlevel, distortionlevel, amplitude, squish, lowvibratorange, hivibratorange, vibratodepth, randomseed, updatefreq, spread, deleteflag)
VFRET1(outskip, duration, pitch, funddecay, nyquistdecay, distortiongain, feedbackgain, feedbackpitch, cleanlevel, distortionlevel, amplitude, lowvibratorange, hivibratorange, vibratodepth, randomseed, updatefreq, spread)
in comment form:
/* START:
p0 = start; p1 = dur; p2 = pitch (oct.pc); p3 = fundamental decay time
p4 = nyquist decay time; p5 = amp, p6 = squish; p7 = stereo spread [optional]
p8 = flag for deleting pluck arrays (used by FRET, BEND, etc.) [optional]
assumes makegen 1 is the amplitude envelope
BEND:
p0 = start; p1 = dur; p2 = pitch0 (oct.pc); p3 = pitch1 (oct.pc);
p4 = gliss function; p5 = fundamental decay time; p6 = nyquist decay time;
p7 = update times/sec; p8 = stereo spread [optional]
assumes makegen 1 is the amplitude envelope
FRET:
p0 = start; p1 = dur; p2 = pitch(oct.pc); p3 = fundamental decay time;
p4 = nyquist decay time; p5 = stereo spread [optional]
assumes makegen 1 is the amplitude envelope
START1:
p0 = start; p1 = dur; p2 = pitch (oct.pc); p3 = fundamental decay time
p4 = nyquist decay time; p5 = distortion gain; p6 = feedback gain
p7 = feedback pitch (oct.pc); p8 = clean signal level
p9 = distortion signal level; p10 = amp; p11 = squish
p12 = stereo spread [optional]
p13 = flag for deleting pluck arrays (used by FRET1, BEND1, etc.) [optional]
assumes makegen 1 is the amplitude envelope
BEND1:
p0 = start; p1 = dur; p2 = pitch0 (oct.pc); p3 = pitch1 (oct.pc)
p4 = gliss function #; p5 = fundamental decay time
p6 = nyquist decay time; p7 = distortion gain; p8 = feedback gain
p9 = feedback pitch (oct.pc); p10 = clean signal level
p11 = distortion signal level; p12 = amp; p13 = update gliss nsamples
p14 = stereo spread [optional]
assumes makegen 1 is the amplitude envelope
FRET1:
p0 = start; p1 = dur; p2 = pitch (oct.pc); p3 = fundamental decay time
p4 = nyquist decay time; p5 = distortion gain; p6 = feedback gain
p7 = feedback pitch (oct.pc); p8 = clean signal level
p9 = distortion signal level; p10 = amp; p11 = stereo spread [optional]
assumes makegen 1 is the amplitude envelope
VSTART1:
p0 = start; p1 = dur; p2 = pitch (oct.pc); p3 = fundamental decay time
p4 = nyquist decay time; p5 = distortion gain; p6 = feedback gain
p7 = feedback pitch (oct.pc); p8 = clean signal level
p9 = distortion signal level; p10 = amp; p11 = squish
p12 = low vibrato freq range; p13 = hi vibrato freq range
p14 = vibrato freq depth (expressed in cps); p15 = random seed value
p16 = pitch update (default 200/sec)
p17 = stereo spread [optional]
p18 = flag for deleting pluck arrays (used by FRET1, BEND1, etc.) [optional]
assumes makegen 1 is the amplitude envelope
assumes makegen 2 is the vibrato function and makegen 3 is the
vibrato amplitude envelope
VFRET1:
p0 = start; p1 = dur; p2 = pitch (oct.pc); p3 = fundamental decay time
p4 = nyquist decay time; p5 = distortion gain; p6 = feedback gain
p7 = feedback pitch (oct.pc); p8 = clean signal level
p9 = distortion signal level; p10 = amp;
p11 = low vibrato freq range; p12 = hi vibrato freq range
p13 = vibrato freq depth (expressed in cps); p14 = random seed value
p15 = pitch update (default 200/sec)
p16 = stereo spread [optional]
assumes makegen 1 is the amplitude envelope
assumes makegen 2 is the vibrato function and makegen 3 is the
vibrato amplitude envelope
*/
An example score:
rtsetparams(44100, 2)
load("STRUM")
makegen(1, 24, 1000, 0,1,1,1)
makegen(2, 2, 7, 7, 7.00, 7.02, 7.05, 7.07, 7.10, 8.00, 8.07)
srand(0.314)
for (st = 0; st < 15; st = st + 0.1) {
pind = random() * 7
pitch = sampfunc(2, pind)
START(st, 1.0, pitch, 1.0, 0.1, 10000.0, 1, random())
}
Another score:
rtsetparams(44100, 2)
load("STRUM")
makegen(1, 24, 1000, 0,1,1,1)
makegen(2, 2, 7, 7, 7.00, 7.02, 7.05, 7.07, 7.10, 8.00, 8.07)
srand(0.314)
for (st = 0; st < 15; st = st + 0.2) {
pind = random() * 7
pitch = sampfunc(2, pind)
stereo = random()
START(st, 0.05, pitch, 1.0, 0.1, 10000, 1, stereo)
FRET(st+0.05, 0.05, pitch+0.07, 1.0, 0.1, stereo)
FRET(st+0.1, 0.05, pitch+0.04, 1.0, 0.1, stereo)
FRET(st+0.15, 0.05, pitch+0.02, 1.0, 0.1, stereo)
}
A third score:
rtsetparams(44100, 2)
load("STRUM")
makegen(1, 24, 1000, 0,1,1,1)
START1(0, 4, 6.08, 1, 1, 10, 0.05, 7.00, 0, 1, 10000, 2)
makegen(2, 24, 1000, 0, 0, 1, 1, 2, 0)
BEND1(4, 4, 6.08, 7.00, 2, 1, 1, 10, 0.05, 7.00, 0, 1, 10000, 100)