i've coded a lot of audio dsp and not much games, so this may be banal as i eg. haven't had much application for bitwise ops. it may not be the best source, perhaps it's still useful. sorriez if i'm duplicating stuf presented better elsewhere.
i enjoy synthesis to the extent where i limit my use of resources - so i stick to procedural audio and sound. i'm also "given" to efficient and "elegant" solutions, which more or less means very particular and narrow minded here's how i have gone about creating sound effects for a simple, ~minimalist game i'm working on -
the most basic element of the coutured sound event is the envelope.. unless the algorithm doesn't require it (eg. mass-spring or sample impulse to decaying resonance). the simplest envelope is linear, but the simplest envelope a person with a self image to protect implements is exponential -
envrate = .6 * SAMPLERATE;
envrate = pow(0.01, 1.0 / envrate);
env = 1.0; // initialise event
env *= envrate; // loop event
the loop also checks if the envelope is below a threshold and .. i implement envelopes with stages in a switch statement .. so terminating the envelope changes the stage. complex envelopes of course can have many stages, and even for single stage envelopes, i may have more than one "active" stage, eg. stage 1 lasts a single sample and recalculates stuf that is modulated, making synthesized events dynamic and more interesting
..and instead of doing this in a simpler way with ints or something, i like to smooth the attack of the envelope (which otherwise may be punishing to the ears with duration) by routing it through a low pass filter. i do this in the most banal fashion for an audio coder as well, 6dB filter
envf = pi2 * 50.f / SAMPLERATE; // w or angular frequency relating hertz to samplerate
envbuf = 0;
envbuf += envf * (env - envbuf); // loop this and use envbuf as the smoothed envelope
note envbuf would have to be treated for denormals if left running, if using a branch to limit when it runs to when it's being fed non-zero data, it's not necessary.
to reproduce the sound exactly, when the envelope is shut off, i would recommend zeroing out the lowpass buffer and other params
so we have an envelope describing an event.. now we need a signal to envelope.. doubles have much better performance tolerances here so i opt for them routinely.
simple case is to use white noise and envelope it, for explosions, all sorts of stuff by changing the envelope time.
my simplest class of explosion sound effects improve this by using the exponential envelope to modify the cutoff of the filter, which can sound very realistic with prudent parameterisation. i route the noise into the lowpass, then apply the envelope to this as the lowpass will apply some smoothing if it has been reset to zero.
the filter frequency has to remain below pi/2 (maybe below 1 theoretically but i have safely ignored this for short events) -
tbh i also streamline this by throwing out the filter cutoff coefficient altogether and replacing it entirely with the scaled envelope.. we want this ....
buf += cutoff * (signal - buf);
we can be "proper"
buf += (1.0 + env * scale) * cutoff * (signal - buf);
or just
buf += env * scale * (signal - buf);
or even drop the scale coefficient if the height of the envelope is convenient. as the sound drops to low frequencies, it is more visceral.
next, i wanted something more than white noise.
one of the main effects i needed was a bouncing sound, which is very dynamic. i used six sine oscillators at ratios of spherical harmonics (analysed basketball, perfect after i tuned down a few octaves..)
oscs can be floats, have two states, real and imag
s0 = 1.f; s1 = 0.f; // initialise
s0 -= w * s1;
s1 += w * s0;
sine output = s1, cosine = s0 (approximately.. "good enuf")
w is modulated in this case but iirc instability occurs above pi/4
there are simplifications of this form that can include decay and stuff. it won't run forever, needs to have the states reset to unity (x-y..) every now and then, or if your events last only a few seconds, just reset to 1 and 0/quadrature.
i could have used nonlinear processing (easy would be out * out * out) or clipping to add harmonics and impact and make a "pretty convincing spherical impact" but just the pitch and dynamic envelope modulation was nice. bounce sounds range from very short to over a second with a wide tonal range than is fun for the object. of course, you could use a sample, and modulate dynamics and pitch envelope as well, but procedural elitism may smite you.
the new part for me in these sounds was creating other complex events in the old fashioned way i know not much about..
the oscillator is initialised with two unsigned shorts. the selection of starting values is a tonal parameter.
..these settings gave me a nice gritty crunching sound i used as an impact sound -
switch (hs) {
case 1:
h0 = 30090; h1 = 31800;
hs = 2;
break;
case 2:
h0++; h1++;
(h0 & (h1 >> 2)) > 200 ? o = 24.f : o = -24.f;
hb += (1.0 + he * 19.0) * hf * (o - hb);
o = hb * he; outl += o; outr += o;
he *= hr; if (he < .0001) hb = he = hs = 0;
iirc the envelope was 0.6 seconds.. obviously changing any of these values or structure will affect the tone. i developed this sound from a sort of apple biting sound.. changing the >>2 to higher values produces increasingly slower stepping sounds.. bells and dings that i'm sure i've heard in games before. it's not the most amazing range ever, but if you dig around it does have several "ranges" of timbres that can be adapted to various things. of course, just incrementing two ints is not very exciting. the trick is to find possibilities that oscillate and of course there are many ways to do this.
also produced some cyclic sounds by eg. running the switch statement on a >> or / so incrementing steps or loops through coefficient sets.
so, a starting point, experiment.
i dunno maaan, i guess if there were a pac-man sfx guide i figure i'd probably have heard about it in my field? maybe not