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
2. Starting And Learning
3. What Do You Want?
4. Learn By Example
5. Setting Up Your Work Room On Your B0x
6. No Code Yet?
7. A Little Distortion Effect
7.1 How Does Distortion Work
7.2 Starting The Project
7.3 The Machine Paperwork
7.4 The Distortion Code
8. A Basic Lowpass Filter
8.1 Understanding Making a Filter
8.2 Making The Filter
8.3 The Whole Filter Code
8.4 How Do I Test Filters?
8.5 Now How About Inertia?
9. Convolver Effect
10. A Ring Modulator
11. DC Removal
12. Better About Dialogs
12.1 About Box With Scrollable Text Box
13. Submenus On Machine Right Click
14. Using Non-Modal Dialogs
15. Working With Dialog User Interface Elements
16. Adding Attributes and cautions with Updates
17. Saving And Loading Custom Data
18. Adding Stereo Entries in the Wavetable
A. AtomStereoMeld Source Code
B. Classes for Multiple Non-Modal Dialogs
C. Stereo to Mono Source Code
D. Guru4 Type Filter Source Code


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[] = {
            &paraTopTresh,
            &paraBottomTresh,
            &paraTopClamp,
            &paraBottomClamp,
            &paraDryOut
};
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[] = {
            &paraTopTresh,
            &paraBottomTresh,
            &paraTopClamp,
            &paraBottomClamp,
            &paraDryOut
};
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[] = {
            &paraCutoff,
            &paraResonance
};
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