|
|
|
|
|
|
|
These are some of the biggest mistakes that I see being made over and over
again. If you are new to machine making
(or even if you have already released machines), please read over this.
1. Wavetable access
If your machine access the wavetable, you MUST get the pointer to the
wavelevel structure IN THE WORK FUNCTION,
not in the Tick function. If the user loads a new wave into the wavetable
over the one you are playing, your pointer
will no longer be valid. This means your machine will crash buzz and the user
will probably lose the song they are
working on. This is not good. New waves are not loaded until after the Work
functions are finished, so it is safe to
get the pointer here.
2. WM Errors
When your machine is not producing output, your work function MUST RETURN
FALSE. If it doesn't, it wastes lots
of CPU, and can also lead to floating point errors which will bring the
system to a halt. Think about it - if
you have a generator at the begining of a chain which is returning true, even
though it is not giving any output,
all the effects after it in the chain are on, and thus taking up CPU. If you
have a Raverb at the end, then this is
a big waste. Here is a quick rundown of what the WM_ functions do:
WM_NOIO - No input / output
WM_READ - This means the machine (probably an effect) has an input connected
to it, but no outputs (or maybe the outputs are muted).
WM_WRITE - This means the machine has no input waiting for it, but output
will be sent (if your effect is a delay, you may want to finish sending any
echoes during this phase).
WM_READWRITE - the normal machine state, input and output
If you are not sure if your machine is returning the proper value, you can
check the leds on the machines to
see if they stay on after the machine should be off, and also thru the debug
option on the CPU usage menu.
3. Pops and Clicks
A pop occurs whenever you have a large, quick change in amplitude. This
change is usually too fast for the speaker
to handle, and causes an audible click. In order to avoid pops, you must put
a slight ramp in the volume whenever
you play a sample or a wave (ie, fade it in). Even if you don't have an
envelope, it is a good idea, to do a quick fade
over 50 samples or whatever seems like a good idea. These ramps should occur
at both the begining and end of a sound,
since the amplitude will be changing from zero to some value, or some value
to zero, once the sound starts or stops.
Also, if one note interrupts another, you may get a pop if your amplitude
goes right back to zero for the new wave.
An easy thing to do would be to keep the volume for the new wave, and not
fade in. This still may allow some pops though.
The idea solution would be to fade out the old wave while the new wave is
fading in. This provides the smoothest transitions.
4. Optimize
I can't emphasize this enough. The early buzz machines were VERY fast.
These days people don't seem to care how
much CPU it takes up, as long as it sounds ok. This is not a very good
attitude, since the slower the machines are,
the less you will be able to use in a song. To make sure your machine is as
fast as possible, be sure to optimize
your loops. Any values that stay constant for the duration of the loop can be
precalculated before the loop. Any other
values that can be precalculated and possibly put in a table will save time
also (please, lets avoid massive tables though).
Some operations that are really slow are Pow(), Cos(), Sin(), %, and
converting from Float to Int. A trick to avoid
doing % with binary numbers (2, 4, 8, 16), is to do this:
x % 16 becomes x & 15 or in other words x % a = x & (a - 1)
This will yield the same result and is much faster. Also, multiply is faster
than divide, so * 0.5, is faster than / 2.0
if you are using floating point numbers. For integers, you can multiply by
binary numbers very fast using shift operations.
A shift left is the same as multiplying by 2 and a shift right is the same as
dividing by two. Most people are already aware of this trick.
If you must use Pow, Cos or any other math function in your loop, try to put
this info in a table. If it's a waveform you need, you can access any of the
default oscillators using the
GetOscillatorTable function. Finally, there are often special cases in audio
(ie, a delay with no feedback can be
done with one less multiply). Try to figure out what these cases are, and
optimize for them if they occur frequently.
Finally, if you have a small function which is used often, declare it as
inline. Remember that it takes time
to call a function, so it may just be faster to put your code in the same
routine if it is not a lot of code.
Always pay attention to how often each part of your code is done, and
optimize the parts that get run most often,
as much as you can. Floating point is fairly fast on today's computers, but
fixed point math is still faster in most
cases. Look elsewhere for more info on fixed point math if you are unsure
what it is.
5. Aliasing
Almost every new synth gen that comes out has this problem. Aliasing
occurs when you try to play frequencies that are
higher than Nyquist frequency (which is half the sample rate). So, for
44100hz audio, the highest frequency you can
play without aliasing is 22050 hz. When you play a waveform at a specific
note, you get frequencies at that note,
and harmonic frequencies which are higher than that note. So, say you have a
wave sampled at C-4 which contains
all the harmonics up to the highest frequency which can be represented. If
you try to play that wave at C-5, some
of those frequencies will be above the highest frequency (the aforementioned
Nyquist value), and will alias, causing
lower, disharmonic frequencies to emerge. Aliasing usually sounds pretty bad.
This is especially annoying, since
with any of the default oscillator waveforms, aliasing tends to become
noticable around the 5th and above octaves.
Oskari has provided a way around this problem though most people either don't
understand or are too lazy to do it.
The answer is to keep band-limited versions of the waveforms which don't have
as many harmonics and won't alias when
used in the upper octaves. When you use GetOscillatorTable function, it
returns the address of the lowest bandlimited
wave, which is size 2048. Next to it in the table is the next wave, sized
1024, then another at 512, ect. The highest
level you can use is level 10, which is size 4. To decide what level to use,
pick the wave size so that your step (rate)
per sample is one. This is the algorithm I use to do it - There is probably a
faster way. As you can see,
this could be optimized by precalculating Frequency/SamplesPerSec. You
wouldn't want to use this in your main loop.
LevelShift1 = 0;
while(Frequency*((float)(2048>>LevelShift1)/(float)pmi->pMasterInfo->SamplesPerSec) > 1.0)
LevelShift1++;
if(LevelShift1 > 10)
LevelShift1 = 10;
You will also need to do some interpolation of your points (since the higher
waveforms contain very few points).
Linear is the minimum.. Cubic spline is good, if you can get it fast enough.
6. Notes
The way notes work in buzz is a little wierd, compared to midi. Instead of
every 12 notes being an octave, it's
16. So, to find out what octave a note is in, you must divide by 16. The
notes themselves are still 0-11 in each octave,
so to find what note it is, modulus by 16. The last four numbers are ignored.
The numbers were picked this way so they
could easily be done with logical operations. If you don't do this properly,
your machine will be out of key and unusable.
7. Inertia
It's always annoying when you try to tweak a parameter only to find that
there is no smooth interpolation between
values. Without inertia, tweaking sounds mechanical and quantized. Not
exactly what you want for your smooth filter
sweeps. So, please add some glide time for your parameters whenever a value
is changed. Slowly go from one value to
the next over each sample that is produced. Inertia is only needed where it
makes sense that a person would want
to change a parameter in real time. Otherwise, the extra overhead needed to
do inertia on all the parameters would be very slow.
8. Midi
This isn't a mistake so much as something most people overlook.
Adding midi to generators is VERY easy. Here is some code originally by
oskari with some additions by me.
void mi::MidiNote(int const channel, int const value, int const velocity)
{
int v2;
if(aval.MIDIChannel != 0)
if(channel != aval.MIDIChannel-1)
return;
v2 = value + aval.MIDITranspose-24;
if (v2 / 12 > 9)
return;
byte n = ((v2 / 12) << 4) | ((v2 % 12) + 1);
if (velocity > 0)
{
for (int c = 0; c < numTracks; c++)
{
if (Tracks[c].Note == NOTE_NO)
{
Tracks[c].Note = n;
if(aval.MIDIVelocity == 1) // 0 = ignore velocity
Tracks[c].Volume = velocity<<20;
Tracks[c].NoteOn();
return;
}
}
}
else
{
for (int c = 0; c < numTracks; c++)
{
if (Tracks[c].Note == n)
{
Tracks[c].NoteOff();
return;
}
}
}
}
This will allow you to take midi input from keyboards and such and play as
many notes at once as you have
tracks for in the machine. It also checks an attribute called MIDIChannel if
it's set to 1-16. This will
allow different generators to be controlled from different midi sources if
you desire. If your sound card has
low enough latency, you set up your driver right, and you actually have a
keyboard, this is a pretty cool feature.
Some things I'd like to see with midi control - samplers, drum machines, ect.
That's all for this week, stay tuned for future editions.
WhiteNoise (LowPass) - admin@whitenoiseaudio.com - www.whitenoiseaudio.com
|
|
|
|
|
|
|