A "SMALL" GUIDE ON MAKING A EFFECT FOR BUZZ
First Edition
Written by CyanPhase (Edward L. Blake)
Additional stuff by Mikko Apo
1. SO YOU WANT TO MAKE A BUZZ EFFECT
You probably wanted to do this for one of several reasons, one of them
being possibly that it's hard to find a dev who wants to code the buzz
effect of your dreams, maybe you want fame, maybe you want to explore
what's out there, try new things regardless what others would say,
maybe there is even a key functionality in buzz missing...
The list goes on...
Anyway, to start programming a machine, in most cases you will require
a pretty specific programming tool to make the machines, which is
Visual C++ 5.0, or 6.0. You might even need the professional Edition
to take advantage of optimizations (the differences between
the buzz machines compiled with Visual C++ standard or Visual C++
professional is quite apparent in terms of CPU usage).
Don't need to run to your local university for a copy just yet, I
recommend to read at least most of this before starting! :-)
2. STARTING AND LEARNING
The advantage with the way Buzz Machines were designed is apparently
the very easy and simple way a buzz machine can be created. Compared
to a standalone application, the simpler Buzz machine effects will
never need to access the hard drives, handle the GUI, or use messy
window procedures (unless you want to have a nice decent about box or
anything more complex). You can learn more or less the basics of the
C\C++ language and get pretty much started. Simply pick up a pretty
good book at the book store and start reading. Some effects you will
want to code might just have adding, subtracting, multiplying,
dividing, and so on.
You should have a pretty good knowledge about at least some
fundamentals about OO, arithmetic and math, procedures, and some
rudimentary knowledge about about pointers.
3. WHAT DO YOU WANT?
What is this machine your planning to make is supposed to do? Of
course, you cannot just jump in and code something unless you know
pretty well what you want. Is it a insane 9 oscilator ring mod? is
it a DC offset equalizer? is it a filter? a 4 band equalizer?
Remember if your not familiar with this sort of territory, you should
start small and build from there.
The more advanced concepts are riddled with math equations from space,
calculus, DSP theory, strange words you've never heard before, and so
on.
A good starting point is to build a effect where you can visualize the
signal pretty well... imagine things like as you see it zoomed in
within Cool Edit, it's a bunch of dots (which are the samples, more
on that later), each with a certain height from the middle line (the
value of the sample). That middle line is where the sample is near 0.
The next concept to understand is the buzz machines, HOW DO THEY
WORK!??, well, people who haven't seen the source, or are just not
sure where everything fits in, would probably assume some strange
voodoo moves the sound from point A to B. Actually the way Buzz works
is by processing chunks of sound over and over again. What do I mean
by chunks of sound? I mean that one procedure in the buzz machine is
called each time, passing with it a pointer to a little packet of
samples, which then the procedure works on them, and leaves the
procedure. This process is done several times a second, so Buzz does
not apparently look slowed down.
But then how does the machine knows when and how to do it? where does
the parameters come in, how does the machine know when a parameter
changed? The parameters are not processed within the same procedure
as with the chunks of samples, unlike you probably have seen with
general Windows programming, there is not a single procedure handling
everything, calling other functions when needed, or process complex
WM_ messages. Buzz machines have a few procedures, which buzz simply
recognizes and will use them depending on whats to be done. If you
have ever used Delphi, MFC with Visual C++, or Visual Basic, it's
pretty similar. It's like a Event driven architecture.
The average small effect usually uses some of these procedures:
mi()
~mi()
void Tick()
void MDKInit(CMachineDataInput * const pi)
bool MDKWork(float *psamples, int numsamples, int const mode)
bool MDKWorkStereo(float *psamples, int numsamples, int const mode)
void Command(int const i)
void MDKSave(CMachineDataOutput * const po)
char const *DescribeValue(int const param, int const value)
Do any of these look confusing? maybe? maybe not? Anyway, following
is a short description of these procedures.
mi()
In OO, this is the constructor of the machine interface class. you
can do some little setup things here, but usually it's just to set
up references to your global and track parameters, and your
attributes The machine interface itself is like the "core" of your
buzz effect.
~mi()
In OO, this is the destructor of the machine interface class.
Usually this is empty but it can include some code to clean up
after some memory buffers you've created, etc.
void Tick()
This is the procedure where you get to see most parameter changes.
If your machine has no parameters it's obvious you can leave this
procedure empty.
void MDKInit(CMachineDataInput * const pi)
This is the procedure to put the startup values of your machine
into, it's also the place to pull out values you saved off with the
MDKSave procedure.
bool MDKWork(float *psamples, int numsamples, int const mode)
This is a procedure to handle mono chunks of samples. Since it's
mono, I really don't recommend using it. In fact I won't go much
into it :-) Besides at this time of age, I don't like to see mono
effects released, they are a mess when you want a nice untarnished
stereo soundscape in your songs.
bool MDKWorkStereo(float *psamples, int numsamples, int const mode)
This is the recommended procedure to use (as it will be a lot less
of a pain for me to use your machine anyway) for processing the
chunks of samples, the way the chunks are processed are a bit
different with this procedure, but we will look into that later.
void Command(int const i);
The icing on the cake! you can add extra menu commands to make your
own about box and stuff.
void MDKSave(CMachineDataOutput * const po);
This is where you get to save data you don't have part of the
parameters, DONT USE IT FOR PARAMETER DATA, that'd be a waste of
time, since buzz handles that for you. :-)
char const *DescribeValue(int const param, int const value);
This is another cool procedure somewhat related to the GUI,
whatever you send back from this procedure will be the values
printed off on the right side of your buzz effect's parameter
window sliders. It also appears in the pattern view in the
statusbar.
Note that there is a lot more definable functions you can use than
this, some we will look into later, and some others are just plain
too advanced for a primer like this.
4. LEARN BY EXAMPLE
There are a few places where you can find out more about machine
programming. Check the following addresses:
Buzz Centric
MoEval's BuzzDev Trials is a MUST! it's hard to believe this
is getting harder and harder to find:
http://www.z00m.org/~moeval/software.html
I hope MoEval keeps that file up for as long as possible :),
its great to learn from others how this buzz stuff works.
Included in BuzzDev Trials 1999
BuzzMachines.com has a developer links section:
http://web.hibo.no/~mva/devlinks.php
There's a sweet tutorial by Steve Horne (Ninereeds) that also
covers generator. It's scope is a bit different than this one,
partly the difference in age ;) and how deep these tutorials
go. I still recommend however to get it as it will help as another
source of information.
http://www.lurking.demon.co.uk/ (Unavailable?)
SurfSmurf mirrored this cool article: http://fuel.adsl.dk/Buzz/
BuzzFAQ (operated by Mikko Apo) at http://go.to/buzzfaq has a thread
about starting out at making buzz machines.
http://pub10.ezboard.com/fbuzzfaqanswers.showMessage?topicID=37.topic
Jeskola.com has some of the latest development files you'll need
to work with this tutorial. Some of the rest of the useful buzz
API (like auxbus) can be found in the BuzzDev Trials.
http://www.jeskola.com/buzzdev
http://jeskola.com/dev.html
vII used to have a short but decent page about developing Buzz
machines.
Ever since the last BuzzDev Trials update there have been some
source code lying around the net that might be useful.
Cheapo amp (v1.0) [http://www.hut.fi/%7Emapo/buzz/]
Cheapo dc (v2.01, v2.0, v1.0, Pre-release 2) [http://www.hut.fi/%7Emapo/buzz/]
Cheapo do-nothing (v1.0) [http://www.hut.fi/%7Emapo/buzz/]
Cheapo fixer pro (v1.0) [http://www.hut.fi/%7Emapo/buzz/]
Cheapo fixer (v1.03, v1.0) [http://www.hut.fi/%7Emapo/buzz/]
Cheapo negative (v1.0) [http://www.hut.fi/%7Emapo/buzz/]
Cheapo protection (release candidate 1) [http://www.hut.fi/%7Emapo/buzz/]
Cheapo statistics (v1.04, v1.02, v1.01, v1.0) [http://www.hut.fi/%7Emapo/buzz/]
Cheapo stereo xfade (v1.0) [http://www.hut.fi/%7Emapo/buzz/]
CyanPhase DTMF-1 (both v1.0 and v1.1) [http://www.buzzmachines.com]
CyanPhase AtomStereoMeld (v1.0, in this file) [http://www.buzzmachines.com]
CyanPhase Mono (both v1.0) [http://www.buzzmachines.com]
Frequency Unknown O-Delay (v1.0) [http://www.buzzmachines.com]
Q Rebond (v1.0) [http://www.buzzmachines.com]
Q Zfilter (v1.0) [http://www.buzzmachines.com]
Zephod SuperFM (was on buzztrack) [http://www.BuzzTrack.com]
Zwar 11-Stereo (v1.0) [http://www.buzzmachines.com]
Zwar 11-A2M (v1.0) [http://www.buzzmachines.com]
Zwar 11-CSI (v1.0) [http://www.buzzmachines.com]
General DSP
Harmony Central
http://www.harmony-central.com/Computer/Programming
MusicDSP Mailing List Archives
http://www.smartelectronix.com/musicdsp
This is a pretty cool resource too :) lots of source code for different
things, like optimization and filters.
they are on efnet too at #musicdsp
5. SETTING UP YOUR WORK ROOM ON YOUR B0X
You might want to first set up your folders up right the first time,
it will help a lot to prevent fustration. I personally use this setup
(and this is the setup used by this guide):
c:\
projects\
My Machine 1\
Debug\
Release\
My Machine 2\
Debug\
Release\
My Machine 3\
Debug\
Release\
auxbus\
auxbus.h
auxbus.lib
dsplib\
bw.h
dsplib.h
resample.h
rswrap.h
dsplib.lib
mdk\
mdk.lib
mdk.h
machineinterface.h
dsplib.dll
am3000.cpp
machineinterface.h
Note that when your creating a project, make sure the base folder the
projects go (not the project folder itself) is the Projects folder.
or you'll run into problems referencing the libs and .h files.
6. NO CODE YET?
Some of you might be wondering what kind of programming is this?...
I haven't spoken a word about code yet, well, I will. However I will
say that I feel that source code is not synonymous with the big
picture of things. Source code is a tool, it's the tools to build your
house, but the tool is not house. You should learn how a house is
built with plain understanding, not with a instruction manual and a
hammer ;-).
Also besides that, this guide is here to give you a bit of a taste on
buzz machine programming without actually touching a compiler yet.
Anyway, here is where we will start.
In this primer we will start by building a pretty simple distortion
effect, a ring modulator effect and afterwards we will then to make
a simple analog lowpass filter.
7. A LITTLE DISTORTION EFFECT
Why is there so many distortion machines that come with buzz? I'm not
too sure, but we'll start by creating a machine that clips input and
optionally reassigns it.
7.1 HOW DOES DISTORTION WORK
there is a whole universe of distortion effects, including but not
limited to clipping, waveshapers, non-linears, etc. In extreme
settings, even some dynamics components like compressors and expanders
can be considered distortion (clippers and noise gates).
In many cases (but not all), a distortion is characterized
distinctively by it's involvement in the actual waveforms you'd see
in a oscilloscope view instead of in the spectral frequency view. but
generally a distortion's application is to create a bunch of rich odd
and even harmonics and frequencies out of a few bandlimited frequency.
We'll build in this example a basic distortion effect that clips the
top and the bottom of the input.
7.2 STARTING THE PROJECT
Start up MSVC++, and go to File > New...
Select the "Projects" tab and select the project type
"Win32 Dynamic-Link Library", it's usually the second to the last,
Make sure your folder thing reads "C:\Projects\" or whatever you've
named that master work folder, now name your project "PDist", your
work folder should now read "C:\Projects\PDist", this is OK! don't
change it, and press enter.
Choose "an empty DLL project" and continue.
After the project is created, go to File > New... again
and select under the "Files" tab the "C++ source file".
name it "PDist".

From the Build > Set Active Configuration, choose "Win32 Release".
Click on the FileView tab on the view pane on the left. it will
show the project files. Right click on "Ome PDist files" and click
"Settings..."

Go to the "C\C++" tab and go through the options and change these
in particular:
Code Generation:
Processor: Pentium
Use runtime library: Multithreaded DLL
Calling convention: __fastcall
Struct member align: 4 bytes
Optimizations:
Maximize Speed
Inline functions: Any Suitable
And go to the "Link" tab for these:
General:
object\library modules: add ../mdk/mdk.lib at the end
of the long line
Click OK to exit the dialog
Now you can start to code
7.3 THE MACHINE PAPERWORK
Most machines start out with some sort of common skeleton
--------
#include "../mdk/mdk.h"
#include <windows.h>
#pragma optimize ("awy", on)
CMachineParameter const paraTopTresh =
{
pt_word, // Parameter data type
"Top-T", // Parameter name as its shown in the parameter
// window
"Top Treshold", // Parameter description as its shown in
//the pattern view's statusbar
0, // Minimum value
0xFFFE, // Maximum value
0xFFFF, // Novalue, this value means "nothing
// happened" in the mi::Tick procedure
MPF_STATE, // Parameter options, MPF_STATE makes it
// appears as a slider
0xFFFE // the default slider value
};
CMachineParameter const paraBottomTresh =
{
pt_word, // Parameter data type
"Bottom-T", // Parameter name as its shown in the parameter
// window
"Bottom Treshold",// Parameter description as its shown in the
// pattern view's statusbar
0, // Minimum value
0xFFFE, // Maximum value
0xFFFF, // Novalue, this value means "nothing
// happened" in the mi::Tick procedure
MPF_STATE, // Parameter options, MPF_STATE makes it
// appears as a slider
0xFFFE // the default slider value
};
CMachineParameter const paraTopClamp =
{
pt_word, // Parameter data type
"Top-Clamp",// Parameter name as its shown in the parameter
// window
"Top Clamp",// Parameter description as its shown in the
// pattern view's statusbar
0, // Minimum value
0xFFFE, // Maximum value
0xFFFF, // Novalue, this value means "nothing
// happened" in the mi::Tick procedure
MPF_STATE, // Parameter options, MPF_STATE makes it appears as
// a slider
0xFFFE // the default slider value
};
CMachineParameter const paraBottomClamp =
{
pt_word, // Parameter data type
"BottomClamp", // Parameter name as its shown in the parameter
// window
"Bottom Clamp", // Parameter description as its shown in the
// pattern view's statusbar
0, // Minimum value
0xFFFE, // Maximum value
0xFFFF, // Novalue, this value means "nothing
// happened" in the mi::Tick procedure
MPF_STATE, // Parameter options, MPF_STATE makes it
// appears as a slider
0xFFFE // the default slider value
};
CMachineParameter const paraDryOut =
{
pt_byte, // Parameter data type
"Dry Out", // Parameter name as its shown in the parameter
// window
"Dry Out", // Parameter description as its shown in the pattern
// view's statusbar
0, // Minimum value
0xFE, // Maximum value
0xFF, // Novalue, this value means "nothing happened" in
// the mi::Tick procedure
MPF_STATE, // Parameter options, MPF_STATE makes it appears as a
// slider
0 // the default slider value
};
CMachineParameter const *pParameters[] = {
¶TopTresh,
¶BottomTresh,
¶TopClamp,
¶BottomClamp,
¶DryOut
};
CMachineAttribute const *pAttributes[] = { NULL };
--------
Now lets go through these different things:
#include "../mdk/mdk.h"
You'll need this to make most of the modern (stereo in) buzz machine
effects described here. This assumes you have your mdk and stuff set up
as described earlier
#include <windows.h>
You'll need this to use the message box for your about box ;)
#pragma optimize ("awy", on)
Dunno, supposedly it helps, just use it hehe
CMachineParameter const paraTopTresh =
{
pt_word, // Parameter data type
"Top-T", // Parameter name as its shown in the parameter
// window
"Top Treshold",// Parameter description as its shown in the
// pattern view's statusbar
0, // Minimum value
0xFFFE, // Maximum value
0xFFFF, // Novalue, this value means "nothing happened"
// in the mi::Tick procedure
MPF_STATE, // Parameter options, MPF_STATE makes it appears as
// a slider
0xFFFE // the default slider value
};
Each parameter you create needs have something that looks more or
less like this.
Parameter data type
Values: pt_word, pt_byte, pt_switch, pt_note
What type of data the parameter is
Parameter name as its shown in the parameter window
Values: A short string that can fit in the parameter window.
This is shown both in the pattern view's status bar and on the
left of the parameter's slider in the parameter window.
Parameter description as its shown in the pattern view's statusbar
Values: A string of text
This is usually a more verbose version of the parameter name.
Minimum value
Values: depends on data type, 0 to 65535 for pt_word,
0 to 255 for pt_byte, -1 for pt_switch, WAVE_MIN if
MPF_WAVE is set (see below), NOTE_MIN if pt_note.
Sets the minimum value of the parameter. the parameter will
never go anywhere lower than this value.
Maximum value
Values: depends on data type, 0 to 65535 for pt_word,
0 to 255 for pt_byte, -1 for pt_switch, WAVE_MAX if
MPF_WAVE is set (see below), NOTE_MAX if pt_note.
Sets the maximum value of the parameter. the parameter will
never go anywhere higher than this value.
Novalue, this value means "nothing happened" in the mi::Tick procedure
Values: depends on data type, 0 to 65535 for pt_word,
0 to 255 for pt_byte, SWITCH_NO for pt_switch, WAVE_NO if
MPF_WAVE is set (see below), NOTE_NO if pt_note.
Sets the NoValue value of the parameter. this special value
basically means that "nothing happened" when its processed in
the mi::Tick. it's best to make sure this value is not within
the range set by the minimum and maximum values. usually a good
setup is to have this 0xFFFF as much as possible, to make your
code less confusing to debug and more easier to create the
mi::Tick section.
Parameter options, MPF_STATE makes it appears as a slider
Values: MPF_STATE, MPF_WAVE, MPF_TICK_ON_EDIT
This will affect some of the behavior of the parameter,
MPF_STATE makes the parameter become a slider instead of
just a column in the pattern view. to have the opposite (usually
for note entry, command effects columns, etc) set to 0 to only
show it in the pattern view.
MPF_WAVE turns the parameter into the "official" wave number
column which is useful usually if the machine is a tracker (out
of context in this doc because it's targeted to writing effects).
MPF_TICK_ON_EDIT usually is used to trigger a mi::Tick whenever
a value is changed in that particular column in the pattern view.
The default slider value
Values: depends on data type, but should be between min value
and max value.
CMachineParameter const *pParameters[] = {
¶TopTresh,
¶BottomTresh,
¶TopClamp,
¶BottomClamp,
¶DryOut
};
CMachineAttribute const *pAttributes[] = { NULL };
All the parameters and attributes should be listed in this format
after they were defined.
Now you should add this stuff (after the parameter stuff):
------------
#pragma pack(1)
class gvals
{
public:
word toptresh;
word bottomtresh;
word topclamp;
word bottomclamp;
byte dryout;
};
#pragma pack()
CMachineInfo const MacInfo =
{
MT_EFFECT, // Machine type
MI_VERSION, // Machine interface version
MIF_DOES_INPUT_MIXING, // Machine flags
0, // min tracks
0, // max tracks
5, // numGlobalParameters
0, // numTrackParameters
pParameters, // pointer to parameter stuff
0, // numAttributes
pAttributes, // pointer to attribute stuff
"Ome PDist", // Full Machine Name
"PDist", // Short name
"A BuzzDev Ex.", // Author name
"&About..." // Right click menu commands
};
class miex : public CMDKMachineInterfaceEx { };
class mi : public CMDKMachineInterface
{
public:
mi();
virtual ~mi();
virtual void Tick();
virtual void MDKInit(CMachineDataInput * const pi);
virtual bool MDKWork(float *psamples, int numsamples, int const mode);
virtual bool MDKWorkStereo(float *psamples, int numsamples, int const mode);
virtual void Command(int const i);
virtual void MDKSave(CMachineDataOutput * const po);
virtual char const *DescribeValue(int const param, int const value);
virtual CMDKMachineInterfaceEx *GetEx() { return &ex; }
virtual void OutputModeChanged(bool stereo) {}
public:
miex ex;
public:
float toptresh, bottomtresh;
float topreassign, bottomreassign;
float dryoutamt;
gvals gval;
};
----------
Time to go through these things:
#pragma pack(1)
class gvals
{
public:
word toptresh;
word bottomtresh;
word topclamp;
word bottomclamp;
byte dryout;
};
#pragma pack()
This section is basically objects that are most accessed by
mi::Tick, there 3 of these classes at the most: gvals, tvals, and
avals. the values sent from buzz to be used by your plugin will be
the values contained in these objects.
CMachineInfo const MacInfo =
{
MT_EFFECT, // Machine type
MI_VERSION, // Machine interface version
MIF_DOES_INPUT_MIXING, // Machine flags
0, // min tracks
0, // max tracks
5, // numGlobalParameters
0, // numTrackParameters
pParameters, // pointer to parameter stuff
0, // numAttributes
pAttributes, // pointer to attribute stuff
"Ome PDist", // Full Machine Name
"PDist", // Short name
"A BuzzDev Ex.", // Author name
"&About..." // Right click menu commands
};
Another section that describes your machine in detail.
Machine type
Values: MT_EFFECTS or MT_GENERATOR (or MT_MASTER?)
This makes buzz figure out if the machine is a effect or a
generator, there is also MT_MASTER, but it's unlikely to be
useful for anything.
Machine interface version
Values: usually MI_VERSION
The version of the interface your machine is using, buzz uses
this usually to figure out how to communicate with the machine
in the best fashion.
Machine flags
Values: MIF_MONO_TO_STEREO, MIF_PLAYS_WAVES, MIF_USES_LIB_INTERFACE,
MIF_USES_INSTRUMENTS, MIF_DOES_INPUT_MIXING, MIF_NO_OUTPUT,
MIF_CONTROL_MACHINE, MIF_INTERNAL_AUX
The flags define the behavior and some options for the machine:
MIF_MONO_TO_STEREO: somewhat obsolete i think in the MDK, makes
your machine take mono input and output in stereo.
MIF_PLAYS_WAVES: tells buzz your machine plays from the
wavetable.
MIF_USES_LIB_INTERFACE: use this with MIF_USES_INSTRUMENTS
MIF_USES_INSTRUMENTS: used for adding a menu to the
new > machine menu (like the VST loader).
MIF_DOES_INPUT_MIXING: allows the machine to read the various
inputs.
MIF_NO_OUTPUT: tells buzz your machine doesn't need to output
to anything.
MIF_CONTROL_MACHINE: tells buzz your machine controls other
machines.
MIF_INTERNAL_AUX: tells buzz your machine uses the internal
aux used by the jeskola mixer and such.
Min tracks
Values: 0 or 1 usually
The least tracks you'll allow in the machine, if you have none,
use 0
Max tracks
Values: 8 and up usually for a gen, 0 for a effect
The most tracks you'll allow in the machine, if you have none,
use 0
numGlobalParameters
Values: based on how many global params
how many parameters are there that are global, they tend to be
the ones with no "#-" at the beginning where # is a number
numTrackParameters
Values: based on how many track params
how many parameters are there that are tracks based, they tend
to be the ones with "#-" at the beginning where # is a number.
pointer to parameter stuff
Values: pParameters[]
just write as is, nothing much to customize here ;)
numAttributes
Values: based on how many attributes
How many attributes is there in pAttributes.
pointer to attribute stuff
Values: pAttributes[]
just write as is, nothing much to customize here ;)
Full Machine Name
Values: This should be in the form of AuthorName MachineName
Just a string of text, generally the name as called like the
DLL name itself.
Short name
Values: The name given as labeled on the machine when created.
This is what appears as the default name when your machine is
created on the machine view, loading another one would make
"[Short Name]2".. "[Short Name]3".. etc.
Author name
Values: Your name
Your name, either real or fictional
Right click menu commands
Values: string
This is in the form "Command 1\nCommand 2\nCommand 3",
use "/Command 1\n" to make a submenu. this creates those menus
you see when right clicking the machine in the machine view.
class miex : public CMDKMachineInterfaceEx { };
class mi : public CMDKMachineInterface
{
public:
mi();
virtual ~mi();
virtual void Tick();
virtual void MDKInit(CMachineDataInput * const pi);
virtual bool MDKWork(float *psamples, int numsamples, int const mode);
virtual bool MDKWorkStereo(float *psamples, int numsamples, int const mode);
virtual void Command(int const i);
virtual void MDKSave(CMachineDataOutput * const po);
virtual char const *DescribeValue(int const param, int const value);
virtual CMDKMachineInterfaceEx *GetEx() { return &ex; }
virtual void OutputModeChanged(bool stereo) {}
public:
miex ex;
public:
float toptresh, bottomtresh;
float topreassign, bottomreassign;
float dryoutamt;
gvals gval;
};
These are the declares that will make up your machine. whenever
you create a custom function its recommended to add it to these
classes.
And now here is the rest of the code to use:
7.4 THE DISTORTION CODE
mi::mi() { GlobalVals = &gval; }
mi::~mi() { }
void mi::MDKInit(CMachineDataInput * const pi)
{
SetOutputMode( true ); // No mono sounds
toptresh = 65534.0f;
bottomtresh = -65534.0f;
topreassign = 65534.0f;
bottomreassign = -65534.0f;
}
void mi::MDKSave(CMachineDataOutput * const po) { }
void mi::Tick() {
if (gval.toptresh != 0xFFFF) toptresh = (float)gval.toptresh;
if (gval.bottomtresh != 0xFFFF) bottomtresh = -((float)gval.bottomtresh);
if (gval.topclamp != 0xFFFF) topreassign = (float)gval.topclamp;
if (gval.bottomclamp != 0xFFFF) bottomreassign = -((float)gval.bottomclamp);
if (gval.dryout != 0xFF) dryoutamt = gval.dryout / 254.0f;
}
bool mi::MDKWork(float *psamples, int numsamples, int const mode)
{
return false;
}
bool mi::MDKWorkStereo(float *psamples, int numsamples, int const mode)
{
if (mode==WM_WRITE)
return false;
if (mode==WM_NOIO)
return false;
if (mode==WM_READ) // <thru>
return true;
float inL, inR, outL, outR;
int i;
for( i=0; i<numsamples*2; i++ ) {
inL = psamples[i];
inR = psamples[i+1];
outL = inL;
outR = inR;
if (outL > toptresh) outL = topreassign;
if (outL < bottomtresh) outL = bottomreassign;
if (outR > toptresh) outR = topreassign;
if (outR < bottomtresh) outR = bottomreassign;
psamples[i] = outL + inL * dryoutamt;
i++;
psamples[i] = outR + inR * dryoutamt;
};
return true;
}
void mi::Command(int const i)
{
switch (i)
{
case 0:
MessageBox(NULL,"PDist 1.0\n\nThis is an example created from a buzzdev effects tutorial written by CyanPhase","About PDist",MB_OK|MB_SYSTEMMODAL);
break;
default:
break;
}
}
char const *mi::DescribeValue(int const param, int const value)
{
static char txt[16];
switch(param)
{
case 0:
case 1:
case 2:
case 3:
sprintf(txt,"%.1f", (float)value );
return txt;
break;
case 4:
sprintf(txt,"%.1f%%", ((float)value / 254.0f * 100.0f) );
return txt;
break;
default:
return NULL;
}
}
#pragma optimize ("", on)
DLL_EXPORTS
---------
This is the working code as is. Here's some notes and guidelines
for these various functions:
mi::mi()
Usually this function only contains little code, usually only
GlobalVals = &gval; for global parameters.
TrackVals = tval; for track based parameters and
AttrVals = (int *)&aval; for attributes.
mi::~mi()
This usually has almost nothing in it except routines to get rid
of memory spaces and buffers and such.
void mi::MDKInit(CMachineDataInput * const pi)
Put stuff in here you want to have loaded when started. you can
also retrieve extra saved machine-specific data through pi->Read.
SetOutputMode( true ); makes the machine stereo-in only
void mi::MDKSave(CMachineDataOutput * const po)
This has the po->Write thing to save the machine specific data
of your machine. its the same stuff readeable by mi::MDKInit.
void mi::Tick()
Most routines in here should check if the parameter data is
NoValue (0xFFFF and 0xFF in these examples), if it isn't, do
whatever calculations you need with the parameter data.
bool mi::MDKWork(float *psamples, int numsamples, int const mode)
This usually returns false, unless you decide to support mono
mode. you read a sample from psamples, do whats necessary, and
output it back to psamples. do this numsamples times, the mode
variable is important because it tells what the machine is doing.
when mode = WM_WRITE your machine should only output psamples
but don't read what was in psamples.
when mode = WM_NOIO your machine shouldn't do anything to
psamples, just internal processing.
when mode = WM_READ your machine should just read but not write
to psamples, this mode is usually triggered by <thru> in the
sequence editor. just return true if you don't use the data.
when mode = WM_READWRITE, this is the normal mode so to speak,
you read from psamples and output to psamples, these examples
don't test for this because if WM_WRITE, WM_NOIO, and WM_READ
were tested false, it could only be WM_READWRITE.
also, if you return true, the machine outputs the data to the
next machine and its LED is on, if it returns false, the LED is
off, and no data is sent off to the next machine.
bool mi::MDKWorkStereo(float *psamples, int numsamples, int const mode)
you read a sample from psamples, do whats necessary, and
output it back to psamples. do this numsamples times, however
you actually have to do it _twice_, because the left and right
data are interweaved in the psamples buffer. the mode variable
is important because it tells what the machine is doing.
when mode = WM_WRITE your machine should only output psamples
but don't read what was in psamples.
when mode = WM_NOIO your machine shouldn't do anything to
psamples, just internal processing.
when mode = WM_READ your machine should just read but not
write to psamples, this mode is usually triggered by <thru>
in the sequence editor. just return true if you don't use the
data.
when mode = WM_READWRITE, this is the normal mode so to speak,
you read from psamples and output to psamples, these examples
don't test for this because if WM_WRITE, WM_NOIO, and WM_READ
were tested false, it could only be WM_READWRITE.
also, if you return true, the machine outputs the data to the
next machine and its LED is on, if it returns false, the LED is
off, and no data is sent off to the next machine.
void mi::Command(int const i)
This is where menu handlers are put, if you click on the 3rd
menu item in the machine's right click popup. it returns 2
(it starts from 0).
char const *mi::DescribeValue(int const param, int const value)
You send whatever you want back in this function to make the
parameter values shown on the right of the parameter window.
it's important to return something, if no custom value string
is wanted, return NULL, or buzz will die.
#pragma optimize ("", on)
Just put it there.
DLL_EXPORTS
Just put it there too, unless you want to use namespaces
like in the am3000 example.
8. A BASIC LOWPASS FILTER
Now we'll go through making something a little bit harder, but not really
much more, a lowpass filter.
8.1 UNDERSTANDING MAKING A FILTER
A characteristic of most filters is that they must maintain a
"history" of some previous samples. usually the last 2 or 3. Another
characteristic of filters is that they don't always need complex
calculations when it's running real time, in many cases theres never
ifs and conditionals, its mostly the magic of hrm.. math. heh
Depending on the filter you want to make, making a filter can be
pretty easy. in many cases the source code involved is like
plug'n'play ;D. designing custom filters are a bit different tho,
and whole books are written about those, so i'll just describe how
to use the famous cookbook filters in a buzz effect (many machines
in buzz actually uses the cookbook filters).
One thing however about filters is that you must be pretty careful
what your putting in those variables, there are limits to what the
values can be, or else your filter will screw up and will need a
reset of its variables. so when your running the calculations for
your filter, make sure that the cutoff frequency is not around 0
or nyquist (the samplerate divided by 2, usually 22050 in Buzz).
at those points most filters break, or commonly said to "become
unstable".
We'll start a basic filter, it'll have 2 parameters for now:
Cutoff
Resonance
We need to get our hands on a filter, heres one from
Robert Bristow-Johnson's cookbook filters:
LPF: H(s) = 1 / (s^2 + s/Q + 1)
b0 = (1 - cos)/2
b1 = 1 - cos
b2 = (1 - cos)/2
a0 = 1 + alpha
a1 = -2*cos
a2 = 1 - alpha
[Excerpt from RBJ Cookbook filters]
he notes a LOT of stuff at the beginning of that file, heres a few:
The most straight forward implementation would be the
Direct I form (second equation):
y[n] = (b0/a0)*x[n] + (b1/a0)*x[n-1] + (b2/a0)*x[n-2]
- (a1/a0)*y[n-1] - (a2/a0)*y[n-2]
omega = 2*PI*frequency/sampleRate
sin = sin(omega)
cos = cos(omega)
alpha = sin/(2*Q) (if Q is specified)
This is only a few of those things listed in the cookbook, but those
are pretty much the only ones we need :). because some are designed
for the other filters (BP, Notch, LowShelf, HighShelf, PeakEQ) and
such.
8.2 MAKING THE FILTER
In buzz, we can package this up into a function and some variables:
void mi::MaFiltah () {
float alpha, omega, sn, cs;
float a0, a1, a2, b0, b1, b2;
// These limits the cutoff frequency and resonance to
// reasoneable values.
if (param_cutoff < 20.0f) { param_cutoff = 20.0f; };
if (param_cutoff > 22000.0f) { param_cutoff = 22000.0f; };
if (param_resonance < 1.0f) { param_resonance = 1.0f; };
if (param_resonance > 127.0f) { param_resonance = 127.0f; };
omega = 2.0f * PI * param_cutoff/pMasterInfo->SamplesPerSec;
sn = sin (omega); cs = cos (omega);
alpha = sn / param_resonance;
b0 = (1.0f - cs) / 2.0f;
b1 = 1.0f - cs;
b2 = (1.0f - cs) / 2.0f;
a0 = 1.0f + alpha;
a1 = -2.0f * cs;
a2 = 1.0f - alpha;
filtCoefTab[0] = b0/a0;
filtCoefTab[1] = b1/a0;
filtCoefTab[2] = b2/a0;
filtCoefTab[3] = -a1/a0;
filtCoefTab[4] = -a2/a0;
}
In mi class:
class mi : public CMDKMachineInterface
{
public:
mi();
virtual ~mi();
virtual void Tick();
virtual void MDKInit(CMachineDataInput * const pi);
virtual bool MDKWork(float *psamples, int numsamples, int const mode);
virtual bool MDKWorkStereo(float *psamples, int numsamples, int const mode);
virtual void Command(int const i);
virtual void MDKSave(CMachineDataOutput * const po);
virtual char const *DescribeValue(int const param, int const value);
virtual CMDKMachineInterfaceEx *GetEx() { return &ex; }
virtual void OutputModeChanged(bool stereo) {}
// Ma filtah here:
virtual void MaFiltah();
public:
miex ex;
public:
// Filter stuff
float param_cutoff, param_resonance;
float filtCoefTab[5];
float lx1, lx2, ly1, ly2; // Left sample history
float rx1, rx2, ry1, ry2; // Right sample history
gvals gval;
};
here's how it goes to process your samples:
// Left
temp_y = filtCoefTab[0] * outL +
filtCoefTab[1] * lx1 +
filtCoefTab[2] * lx2 +
filtCoefTab[3] * ly1 +
filtCoefTab[4] * ly2;
ly2 = ly1; ly1 = temp_y; lx2 = lx1; lx1 = outL ; outL = temp_y;
// Right
temp_y = filtCoefTab[0] * outR +
filtCoefTab[1] * rx1 +
filtCoefTab[2] * rx2 +
filtCoefTab[3] * ry1 +
filtCoefTab[4] * ry2;
ry2 = ry1; ry1 = temp_y; rx2 = rx1; rx1 = outR ; outR = temp_y;
Nice? ;)
We include the following as well:
#include <math.h>
#include <float.h>
BIG NOTE: set most variables to 0 in the mi::MDKInit function,
the filter can just basically refuse to work otherwise.
heres what the whole thing looks like:
8.3 THE WHOLE FILTER CODE
#include "../mdk/mdk.h"
#include <windows.h>
#include <math.h>
#include <float.h>
#pragma optimize ("awy", on)
CMachineParameter const paraCutoff =
{
pt_word, // Parameter data type
"Cutoff", // Parameter name as its shown in the parameter
// window
"Filter Cutoff", // Parameter description as its shown in
//the pattern view's statusbar
0, // Minimum value
22050, // Maximum value
0xFFFF, // Novalue, this value means "nothing
// happened" in the mi::Tick procedure
MPF_STATE, // Parameter options, MPF_STATE makes it
// appears as a slider
22050 // the default slider value
};
CMachineParameter const paraResonance =
{
pt_byte, // Parameter data type
"Resonance", // Parameter name as its shown in the parameter
// window
"Filter Resonance",// Parameter description as its shown in the
// pattern view's statusbar
1, // Minimum value
0xFE, // Maximum value
0xFF, // Novalue, this value means "nothing
// happened" in the mi::Tick procedure
MPF_STATE, // Parameter options, MPF_STATE makes it
// appears as a slider
0x0 // the default slider value
};
CMachineParameter const *pParameters[] = {
¶Cutoff,
¶Resonance
};
CMachineAttribute const *pAttributes[] = { NULL };
#pragma pack(1)
class gvals
{
public:
word cutoff;
byte resonance;
};
#pragma pack()
CMachineInfo const MacInfo =
{
MT_EFFECT, // Machine type
MI_VERSION, // Machine interface version
MIF_DOES_INPUT_MIXING, // Machine flags
0, // min tracks
0, // max tracks
2, // numGlobalParameters
0, // numTrackParameters
pParameters, // pointer to parameter stuff
0, // numAttributes
pAttributes, // pointer to attribute stuff
"Ome PFilter", // Full Machine Name
"PFilter", // Short name
"A BuzzDev Ex.", // Author name
"&About..." // Right click menu commands
};
class miex : public CMDKMachineInterfaceEx { };
class mi : public CMDKMachineInterface
{
public:
mi();
virtual ~mi();
virtual void Tick();
virtual void MDKInit(CMachineDataInput * const pi);
virtual bool MDKWork(float *psamples, int numsamples, int const mode);
virtual bool MDKWorkStereo(float *psamples, int numsamples, int const mode);
virtual void Command(int const i);
virtual void MDKSave(CMachineDataOutput * const po);
virtual char const *DescribeValue(int const param, int const value);
virtual CMDKMachineInterfaceEx *GetEx() { return &ex; }
virtual void OutputModeChanged(bool stereo) {}
// Ma filtah here:
virtual void MaFiltah();
public:
miex ex;
public:
// Filter stuff
float param_cutoff, param_resonance;
float filtCoefTab[5];
float lx1, lx2, ly1, ly2; // Left sample history
float rx1, rx2, ry1, ry2; // Right sample history
gvals gval;
};
mi::mi() { GlobalVals = &gval; }
mi::~mi() { }
void mi::MaFiltah () {
float alpha, omega, sn, cs;
float a0, a1, a2, b0, b1, b2;
// These limits the cutoff frequency and resonance to
// reasoneable values.
if (param_cutoff < 20.0f) { param_cutoff = 20.0f; };
if (param_cutoff > 22000.0f) { param_cutoff = 22000.0f; };
if (param_resonance < 1.0f) { param_resonance = 1.0f; };
if (param_resonance > 127.0f) { param_resonance = 127.0f; };
omega = 2.0f * PI * param_cutoff/pMasterInfo->SamplesPerSec;
sn = sin (omega); cs = cos (omega);
alpha = sn / param_resonance;
b0 = (1.0f - cs) / 2.0f;
b1 = 1.0f - cs;
b2 = (1.0f - cs) / 2.0f;
a0 = 1.0f + alpha;
a1 = -2.0f * cs;
a2 = 1.0f - alpha;
filtCoefTab[0] = b0/a0;
filtCoefTab[1] = b1/a0;
filtCoefTab[2] = b2/a0;
filtCoefTab[3] = -a1/a0;
filtCoefTab[4] = -a2/a0;
}
void mi::MDKInit(CMachineDataInput * const pi)
{
SetOutputMode( true ); // No mono sounds
param_cutoff = 22050;
param_resonance = 0;
MaFiltah ();
lx1 = lx2 = ly1 = ly2 = 0.0f;
rx1 = rx2 = ry1 = ry2 = 0.0f;
}
void mi::MDKSave(CMachineDataOutput * const po) { }
void mi::Tick() {
if (gval.cutoff != 0xFFFF) {
param_cutoff = (float)gval.cutoff;
MaFiltah();
};
if (gval.resonance != 0xFF) {
param_resonance = ((float)gval.resonance / 2.0f);
MaFiltah();
};
}
bool mi::MDKWork(float *psamples, int numsamples, int const mode)
{
return false;
}
bool mi::MDKWorkStereo(float *psamples, int numsamples, int const mode)
{
if (mode==WM_WRITE)
return false;
if (mode==WM_NOIO)
return false;
if (mode==WM_READ) // <thru>
return true;
float inL, inR, outL, outR, temp_y;
int i;
for( i=0; i<numsamples*2; i++ ) {
inL = psamples[i];
inR = psamples[i+1];
outL = inL;
outR = inR;
// Left
temp_y = filtCoefTab[0] * outL +
filtCoefTab[1] * lx1 +
filtCoefTab[2] * lx2 +
filtCoefTab[3] * ly1 +
filtCoefTab[4] * ly2;
ly2 = ly1; ly1 = temp_y; lx2 = lx1; lx1 = outL ; outL = temp_y;
// Right
temp_y = filtCoefTab[0] * outR +
&nbs