Hence, my first CMIX gong score was born:
What I'm going to do to achieve this is to take the equations for
amp and dur that we used earlier, and multiply them by
a scaling factor which I'll call pitchfactor. pitchfactor
will vary from 1 to 0, depending on how far above 150 Hz the frequency of
the partial is. Thus, I allow partials below 150 Hz to be as long as they
want; but if a partial is above 150Hz, it's going to be shorter and softer
the closer it is to the upper limit of 3000Hz.
the following diagram illustrates:
We want to make an equation out of this. Given a pitch (of an individual partial),
we want to find pitchfactor. So, from our diagram, we see that
the "distance-from-the-top" is directly proportional to pitchfactor.
As "distance-from-the-top" goes up (the actual pitch is going down),
pitchfactor gets larger as well.
So, let us say pitchfactor=dist_from_top/range_of_pitches. Why?
Because then if we're at the top, dist_from_top will be 0, and
0/range_of_pitches=0.
So if our partial lies at the very top of the pitch range, its amp and
dur will be multiplied by, and thereby reduced to, 0. If
on the other hand, the pitch of our partial is at the "bottom" of the range,
dist_from_top will be equal to
the range_of_pitches, and thus dist_from_top/range_of_pitches
will equal 1. So pitchfactor will equal 1. And that's what we want.
So, continuing to refine our equation, dist_from_top=top-pitch or
3000-pitch. Thus, our final equation for pitchfactor is
Hence:
So, here's the whole thing:
So, now onto modelling the sound of twigs jouncing softly against
bicycle spokes . . . .
We begin by creating a single spoke. We treat it simply as a
high gong, with all partials very short.
Hence the first score:
The result of
this
is nice, but perhaps a bit too
"noisy." We want a sound that's a bit more "pitchy." Also,
let's start turning the wheel 'round---i.e. generating more than
one spoke-hit.
So, what we do in the next score is twofold:
So, here it is:
This one definitely sounds more "pitchy." Perhaps a bit too
high
though.
The solution to that is simple enough, just lower the pitch range.
Aaah, yes,
that sounds much better.
Finally, just for experimentation's sake, I decide to try a different
method of getting that slightly "pitchy" sound that I'm after.
I've seen
"gaussian distribution" on the
makegen
page,
and I know that means I've got a makegen that writes a bunch of points,
mostly around a "central value" (0.5 in the case of Gaussian distribution).
If I can get those points to represent pitch values, then using those
frequencies as my spoke-partials might then give me the sound I'm looking for.
So, each time a "spoke-hit" is written, we
generate a new Gaussian distribution of its partials.
This kind of distribution ranges from 0 to 1, "centering"
around .5. So .5 is going to "equal" our
central pitch, which I'm going to call "dukepitch."
The mapping of makegen points onto pitch values is
best illustrated by the following diagram:
To find an individual partial's pitch, we need to know first the
range of pitches available. I decide, for no particular reason, to
have the pitches vary from dukepitch to +/- 45% of dukepitch.
Hence the rangebase will be (dukepitch-(dukepitch*.45))
Then we map the values obtained from our Gaussian makegen via
a sampfunc call onto pitch values with the following equation:
So, here's the whole score for the "gaussian" bicycle spokes:
This wasn't necessarily a better result than I was getting
before, but it's kind of interesting.
possible assignments:
/*basic set-up stuff*/
rtsetparams(44100, 2)
rtoutput("gong0.aiff")
/*use sine-wave as the basic building block
* for partials */
makegen(1, 10, 10000, 1)
/*the envelope is basically a sharp attack,
*followed by quick drop, longer, slightly
*decaying steady-state, and then a drop to 0*/
makegen(2, 24, 10000, 0,1, 1,.5, 8,.4, 10,0)
/*there will be 30 partials */
for (partials=0; partials < 30; partials=partials+1)
{
/*they all begin within .02" of the beginning*/
start=random()*.02
/*pitch varies from 30 to 3000 */
pitch=30+(random()*2970)
/*duration varies from 5 to 15 seconds */
dur=5+(random()*10)
/* amplitude, from 150 to 250---I keep it
* pretty soft to make sure that all 30
* partials added together will never exceed 32768
* max-amp
amp=(150+(random()*100))
/* stereo is just anywhere 'twixt left and right */
stereo=random()
/* write the partial for this loop-pass */
WAVETABLE(start, dur, amp, pitch, stereo)
}
Well, the result of
that
is not bad, but to get a more gong-like sound, I'm going to try something
a little tricky: scaling amplitude and duration to be
inversely proportional to pitch. That is, I want the lower
partials (harmonics) of the sound to be the strongest and longest,
and I want the upper ones to be softer and to die off quickly-er.
That way, hopefully,
we'll get a more boomy oomph to our gong sound.
pitchfactor=(3000-pitch)/2850
In the score, we first assume the pitch is below 150 Hz, and then
decide if that's not true, in which case pitchfactor gets
re-calculated. Later, amp and dur are multiplied by
it to get new values.
/* assume the pitch is lower than 150 */
pitchfactor=1
/* check if it really is */
if (pitch > 150)
{
pitchfactor=(3000-pitch)/2850
}
/* later on . . . . */
dur=(5+(random()*10))*pitchfactor
amp=(150+(random()*100))*pitchfactor
So that's how we deal with the upper partial issue.
The other change we make in this new version of the gong score,
is to add a little bit of
random variation to the previously invariant amplitude envelope.
I do this by inserting variables for the numbers in the makegen
definition. Basically the old values (0,1, 1,.5, 8,.2, 10,0)
are still what's more or less going to happen, but we add
a bit of random jitter to them:
/*makegen variables*/
b=.9+(random()*.2)
/*b is going to vary around 1*/
c=.4+(random()*.2)
/*c around .5 */
d=7.5+random()
/* d around 8, and so on . . . . */
e=.1+(random()*.2)
f=10+random()
/*later*/
makegen(2, 24, 10000, 0,1, b,c, d,e, f,0)
So, here's the whole score:
rtsetparams(44100, 2)
rtoutput("gong1.aiff")
makegen(1, 10, 10000, 1)
/* added: makegen variables
* and factors for pitch height
*/
for (notes=0; notes < 30; notes=notes+1)
{
/*makegen variables*/
b=.9+(random()*.2)
c=.4+(random()*.2)
d=7.5+random()
e=.1+(random()*.2)
f=10+random()
/*our usual wavetable variables*/
start=random()*.02
pitch=30+(random()*2970)
/*default pf to 1, check if
* that's right, re-calculate */
pitchfactor=1
if (pitch > 150)
{
pitchfactor=(3000-pitch)/2850
}
/* scale amplitude and duration */
dur=(5+(random()*10))*pitchfactor
amp=(150+(random()*100))*pitchfactor
stereo=random()
makegen(2, 24, 10000, 0,1, b,c, d,e, f,0)
WAVETABLE(start, dur, amp, pitch, stereo)
}
Well,
this is somewhat of an improvement, but still a little lifeless.
What about those famous tremolating partials that you get with tam-tams?
In the next score, we have a decision-variable that we use to decide
whether or not a partial will be doubled by another copy of itself,
which will be exactly the same, except its frequency will be a few Hz off to
produce beating (tremolo.)
/* this variable is used to "decide" */
trem=random()
/* based on whether it's less than
* or greater than .5 */
if (trem < .5) WAVETABLE(start, dur, amp, pitch, stereo)
Also, I add another loop that simply makes 15 additional partials
below 150 Hz, to add more "beef" to the sound.
rtsetparams(44100, 2)
rtoutput("gong2.aiff")
/* with tremolo
* and, with more lower partials,
* more BEEF!!
*/
makegen(1, 10, 10000, 1)
/* loop for extra lower partials
* Note that there's no pitchfactor stuff
* here, as these partials vary only from
* 20-120 Hz, and thus are too low to involve
* pitchfactor */
for (notes=0; notes < 15; notes=notes+1)
{
/*makegen variables*/
b=.9+(random()*.2)
c=.4+(random()*.2)
d=7.5+random()
e=.1+(random()*.2)
f=10+random()
/*wavetable variables*/
start=random()*.02
pitch=20+(random()*100)
dur=(5+(random()*10))
amp=(150+(random()*100))
stereo=random()
makegen(2, 24, 10000, 0,1, b,c, d,e, f,0)
WAVETABLE(start, dur, amp, pitch, stereo)
/*below, is where a "tremolo" is created.
* Note: 50% of the time; i.e. "if (trem<.5)" */
trem=random()
pitch=pitch+(.5+(random()*5))
if (trem < .5) WAVETABLE(start, dur, amp, pitch, stereo)
}
/* loop for upper partials
* same as last score, but with
* the tremolo thang added*/
for (notes=0; notes < 30; notes=notes+1)
{
/*makegen variables*/
b=.9+(random()*.2)
c=.4+(random()*.2)
d=7.5+random()
e=.1+(random()*.2)
f=10+random()
/*wavetable variables*/
start=random()*.02
pitch=150+(random()*2850)
pitchfactor=(3000-pitch)/2850
dur=(5+(random()*10))*pitchfactor
amp=(150+(random()*100))*pitchfactor
stereo=random()
makegen(2, 24, 10000, 0,1, b,c, d,e, f,0)
WAVETABLE(start, dur, amp, pitch, stereo)
/* tremolo stuff */
trem=random()
if (trem < .5) WAVETABLE(start, dur, amp, pitch+(.5+(random()*5)), stereo)
}
the result of
this sounds pretty cool, if I don't say so myself. . . .
rtsetparams(44100, 2)
reset(44100)
rtoutput("pingk.aiff")
srand(29348723)
makegen(1, 10, 10000, 1)
start=0
dur=0
pitch=400
stereo=.5
for (x=0; x < 100; x=x+1)
{
start=random()*.02
dur=random()*.2+.05
/* this sound is gonna be soft, like
* any self-respecting bike-spoke */
amp=random()*100+50
/* note the pitch range is quite high */
pitch=random()*4000+1800
/* choose 'tween two slightly different
* envelopes. */
chooseenv=random()
if (chooseenv < .5)
{
a=random()*3+2
b=random()*.5+.3
c=a+random()*3+2
d=b/(random()*1+2)
e=c+random()*3+2
makegen(2, 24, 10000, 0,1, a,b, c,d, e,0)
}
if (chooseenv > .5)
{
a=random()*2+1
b=a+random()*4+5
c=random()*.5+.3
d=b+random()*3+2
makegen(2, 24, 10000, 0,0, a,1, b,c, d,0)
}
WAVETABLE(start, dur, amp, pitch, stereo)
}
Hence:
/* the outer loop: 10 spoke-hits */
for (i=0; i < 10; i=i+1)
{
/* the inner loop: 10 partials per hit */
for (x=0; x < 10; x=x+1)
{
/* emperor-start controls when all of the
* partials of a given spoke-hit happen,
* an individual partial's attack-time
* may vary slightly, however. */
start=emperor_start+random()*.02
/* lots of commands and stuff */
}
/* update emperor_start before next spoke-hit */
emperor_start=emperor_start+.2
}
rtsetparams(44100, 2)
reset(44100)
rtoutput("pingk2.aiff")
srand(29723)
makegen(1, 10, 10000, 1)
emperor_start=0
start=0
dur=0
pitch=400
stereo=0.5
/* 10 spoke-hits */
for (i=0; i < 10; i=i+1)
{
/*fewer partials*/
for (x=0; x < 10; x=x+1)
{
/* emperor-start controls when all of the
* partials of a given spoke-hit happen,
* an individual partial's attack-time
* may vary slightly, however. */
start=emperor_start+random()*.02
dur=random()*.2+.05
amp=random()*100+50
pitch=random()*4000+2500
a=random()*3+2
b=random()*.5+.3
c=a+random()*3+2
d=b/(random()*1+2)
e=c+random()*3+2
makegen(2, 24, 10000, 0,1, a,b, c,d, e,0)
WAVETABLE(start, dur, amp, pitch, stereo)
}
emperor_start=emperor_start+.2
}
rtsetparams(44100, 2)
reset(44100)
rtoutput("pingk3.aiff")
srand(29723)
makegen(1, 10, 10000, 1)
emperor_start=0
start=0
dur=0
pitch=400
stereo=0.5
/*lowered the pitch range*/
for (i=0; i < 10; i=i+1)
{
for (x=0; x < 10; x=x+1)
{
start=emperor_start+random()*.02
dur=random()*.2+.05
amp=random()*100+50
pitch=random()*3000+500
a=random()*3+2
b=random()*.5+.3
c=a+random()*3+2
d=b/(random()*1+2)
e=c+random()*3+2
makegen(2, 24, 10000, 0,1, a,b, c,d, e,0)
WAVETABLE(start, dur, amp, pitch, stereo)
}
emperor_start=emperor_start+.2
}
pitch=rangebase+(sampfunc(10, x)*range)
rtsetparams(44100, 2)
reset(44100)
rtoutput("pingk4.aiff")
srand(286723)
makegen(1, 10, 10000, 1)
emperor_start=0
start=0
dur=0
pitch=400
stereo=0.5
/* employ gaussian distribution around a "center pitch"*/
/* more partials, higher amplitude */
for (i=0; i < 15; i=i+1)
{
/* for each spoke-hit, generate a
* a new Gaussian makegen. Note
* 15 points corresponds to 15 partials.
* Also, generate a new dukepitch. */
makegen(10, 20, 15, 4)
dukepitch=random()*1500+900
/*now, the loop for a given spoke-hit.
* 15 partials for each */
for (x=0; x < 15; x=x+1)
{
rangebase=(dukepitch-(dukepitch*.45))
range=(dukepitch*.45)*2
pitch=rangebase+(sampfunc(10, x)*range)
start=emperor_start+random()*.02
dur=random()*.2+.05
amp=random()*200+150
a=random()*3+2
b=random()*.5+.3
c=a+random()*3+2
d=b/(random()*1+2)
e=c+random()*3+2
makegen(2, 24, 10000, 0,1, a,b, c,d, e,0)
WAVETABLE(start, dur, amp, pitch, stereo)
}
emperor_start=emperor_start+.2
}
Boy, I love horizontal rules