A "Small" Guide on Making An Effect for Buzz

From Jeskola Buzz Wiki
Jump to: navigation, search

PLEASE NOTE: THIS TUTORIAL IS VERY OUTDATED, IT WILL COMPILE BUT NOT WORK RIGHT. A NEW TUTORIAL IS IN THE WORKS.


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 or newer. 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! :-)


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.


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 Init(CMachineDataInput * const pi)
 bool Work(float *psamples, int numsamples, int const mode)
 bool WorkStereo(float *psamples, int numsamples, int const mode)
 void Command(int const i)
 void Save(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 Init(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 Save procedure.

 bool Work(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 WorkMonoToStereo(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 Save(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.

Learn by example

TODO (These links need to be checked)

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) [1]
  • Cheapo dc (v2.01, v2.0, v1.0, Pre-release 2) [2]
  • Cheapo do-nothing (v1.0) [3]
  • Cheapo fixer pro (v1.0) [4]
  • Cheapo fixer (v1.03, v1.0) [5]
  • Cheapo negative (v1.0) [6]
  • Cheapo protection (release candidate 1) [7]
  • Cheapo statistics (v1.04, v1.02, v1.01, v1.0) [8]
  • Cheapo stereo xfade (v1.0) [9]
  • CyanPhase DTMF-1 (both v1.0 and v1.1) [10]
  • CyanPhase AtomStereoMeld (v1.0, in this file) [11]
  • CyanPhase Mono (both v1.0) [12]
  • Frequency Unknown O-Delay (v1.0) [13]
  • Q Rebond (v1.0) [14]
  • Q Zfilter (v1.0) [15]
  • Zephod SuperFM (was on buzztrack) [16]
  • Zwar 11-Stereo (v1.0) [17]
  • Zwar 11-A2M (v1.0) [18]
  • Zwar 11-CSI (v1.0) [19]

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

Setting up your source code directory structure

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
     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.

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 the house. You should learn how a house is built with plain understanding, not with an 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.

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.

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.

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

Click OK to exit the dialog

Now you can start to code

The machine paperwork

Most machines start out with some sort of common skeleton

 #include "../MachineInterface.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 "../MachineInterface.h"

You'll need this to make most of the modern (stereo in) buzz machine effects described here.

 #include <windows.h>

You'll need this to use the message box for your about box ;)

 #pragma optimize ("awy", on) 
 //a = No aliasing. (Assume no aliasing occurs within functions)
 //w = Intra-func aliasing. (Assume aliasing occurs across function calls)
 //y = Omit frame pointer. (Suppress creation of frame pointers on call stack. Frees the EBP register for other uses)

Supposedly it helps, just use it.

 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 CMachineInterfaceEx { };
 class mi : public CMachineInterface
 {
 public:
           mi();
           virtual ~mi();
           virtual void Tick();
           virtual void Init(CMachineDataInput * const pi);
           virtual bool Work(float *psamples, int numsamples, int const mode);
           virtual bool WorkMonoToStereo(float *psamples, int numsamples, int const mode);
           virtual void Command(int const i);
           virtual void Save(CMachineDataOutput * const po);
           virtual char const *DescribeValue(int const param, int const value);
           virtual CMachineInterfaceEx *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, 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 CMachineInterfaceEx { };
 class mi : public CMachineInterface
 {
 public:
           mi();
           virtual ~mi();
           virtual void Tick();
           virtual void Init(CMachineDataInput * const pi);
           virtual bool Work(float *psamples, int numsamples, int const mode);
           virtual bool WorkMonoToStereo(float *psamples, int numsamples, int const mode);
           virtual void Command(int const i);
           virtual void Save(CMachineDataOutput * const po);
           virtual char const *DescribeValue(int const param, int const value);
           virtual CMachineInterfaceEx *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:

The distortion code

 mi::mi() {  GlobalVals = &gval; }
 mi::~mi() { }
 void mi::Init(CMachineDataInput * const pi)
 {
           pCB->SetnumOutputChannels(pCB->GetThisMachine(), 2);
           pCB->SetMachineInterfaceEx((CMachineInterfaceEx *)&ex);
           toptresh = 65534.0f;
           bottomtresh = -65534.0f;
           topreassign = 65534.0f;
           bottomreassign = -65534.0f;
 }
 void mi::Save(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::Work(float *psamples, int numsamples, int const mode)
 {
           return false;
 }
 bool mi::WorkMonoToStereo(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::Init(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. pCB->SetnumOutputChannels(pCB->GetThisMachine(), 2); makes the machine stereo-in only pCB->SetMachineInterfaceEx((CMachineInterfaceEx *)&ex); //Setup Extensions as of Buzz 1.2b and later

 void mi::Save(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::Init.

 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::Work(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::WorkMonoToStereo(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.


A basic lowpass filter

Now we'll go through making something a little bit harder, but not really much more, a lowpass filter.

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.


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 CMachineInterface
           {
           public:
                       mi();
                       virtual ~mi();
                       virtual void Tick();
                       virtual void Init(CMachineDataInput * const pi);
                       virtual bool Work(float *psamples, int numsamples, int const mode);
                       virtual bool WorkMonoToStereo(float *psamples, int numsamples, int const mode);
                       virtual void Command(int const i);
                       virtual void Save(CMachineDataOutput * const po);
                       virtual char const *DescribeValue(int const param, int const value);
                       virtual CMachineInterfaceEx *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::Init function, the filter can just basically refuse to work otherwise.

heres what the whole thing looks like:

The whole filter code

  #include "../MachineInterface.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 CMachineInterfaceEx { };
  
  class mi : public CMachineInterface
  {
  public:
            mi();
            virtual ~mi();
            virtual void Tick();
            virtual void Init(CMachineDataInput * const pi);
            virtual bool Work(float *psamples, int numsamples, int const mode);
            virtual bool WorkMonoToStereo(float *psamples, int numsamples, int const mode);
            virtual void Command(int const i);
            virtual void Save(CMachineDataOutput * const po);
            virtual char const *DescribeValue(int const param, int const value);
            virtual CMachineInterfaceEx *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::Init(CMachineDataInput * const pi)
  {
            pCB->SetnumOutputChannels(pCB->GetThisMachine(), 2);
	    pCB->SetMachineInterfaceEx((CMachineInterfaceEx *)&ex);
            param_cutoff = 22050;
            param_resonance = 0;
            MaFiltah ();
            lx1 = lx2 = ly1 = ly2 = 0.0f;
            rx1 = rx2 = ry1 = ry2 = 0.0f;
  }
  
  void mi::Save(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::Work(float *psamples, int numsamples, int const mode)
  {
            return false;
  }

  bool mi::WorkMonoToStereo(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 +
                                    filtCoefTab[1] * rx1 +
                                    filtCoefTab[2] * rx2 +
                                    filtCoefTab[3] * ry1 +
                                    filtCoefTab[4] * ry2;
                        ry2 = ry1; ry1 = temp_y; rx2 = rx1; rx1 = outR ; outR = temp_y;

                        psamples[i] = outL;
                        i++;
                        psamples[i] = outR;
            };
            return true;
  }

  void mi::Command(int const i)
  {
            switch (i)
            {
            case 0:
                        MessageBox(NULL,"PFilter 1.0\n\nThis is an example created from a buzzdev effects tutorial written by CyanPhase","About PFilter",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:
                        sprintf(txt,"%.1f", (float)value );
                        return txt;
                        break;
            case 1:
                        sprintf(txt,"%.1f%%", ((float)value / 254.0f * 100.0f) );
                        return txt;
                        break;
            default:
                        return NULL;
            }
  }
  
  #pragma optimize ("", on) 
  
  DLL_EXPORTS

How do i test filters?

The best way to do thorough tests of your filters is to use the following Setup in Buzz:

[Jeskola Noise]
   '-> [Your Filter Machine] 
           '-> [Geonik Visualization] 
                   '-> [Master]

We'd use geonik viz for two reasons, because of its lower CPU consumption if needed (for lower 2xx processors) and because you can tweek your filter and watch how it affects the input in the spectrum analyzer or scrolling histogram. And to see if it's working as it should.

This page also has a checklist you can go through to catch a few common mistakes.

Now how about inertia?

Inertia is a little detail that takes a little bit of modification to our filter. Basically a basic Inertia allows a cutoff and resonance to increase and decrease smoothly by a delayed step-based variable incrementing\decrementing sorta thing.

The major modifications include that MaFiltah will have to be called on a periodic basis and not during mi::Tick and mi::Init.

A decent rule of thumb before adding a LFO or Inertia to your filter is to make sure your filter is already working without any bugs before adding Inertia support. Test it thoroughly before, because adding a Inertia system gives the filter a MUCH bigger pool for things to happen. Your filter stops being static in time and becomes animated. obscure bugs associated with Inertia can sometimes become fustrating because it becomes sometimes hard to find whats causing it to malfunction.

Ok, so now we'll add some variables to the mi class:

            float dynamic_cutoff, dynamic_resonance;
            float inertiavel, inertiatick;
            int icnt;

Comment out the MaFiltah() calls in mi::Init and mi::Tick

Add a parameter for inertia (shown in complete source below)

Make sure to change the numparameters in MacInfo from 2 to 3.

Add to mi::Init

            dynamic_cutoff = 22050;
            dynamic_resonance = 0;
            inertiavel = (22050.0f / pMasterInfo->SamplesPerSec) * 100.0f;
            inertiatick = 500.0f;

Change mi::Tick to this:
            if (gval.inertia != 0xFFFF) {
                        inertiavel = (float)((param_cutoff - dynamic_cutoff)/(((float)gval.inertia/500.0f) * pMasterInfo->SamplesPerTick)) * 100.0f;
                        if (inertiavel < 0.0f) { inertiavel = -inertiavel; };
                        inertiatick = gval.inertia;
            };
            if (gval.cutoff != 0xFFFF) {
                        param_cutoff = (float)gval.cutoff;
                        inertiavel = (float)((param_cutoff - dynamic_cutoff)/((inertiatick/500.0f) * pMasterInfo->SamplesPerTick)) * 100.0f;
                        if (inertiavel < 0.0f) { inertiavel = -inertiavel; };
            };
            if (gval.resonance != 0xFF) { 
                        param_resonance = ((float)gval.resonance / 2.0f); 
                        inertiavel = (float)((param_resonance - dynamic_resonance)/((inertiatick/500.0f) * pMasterInfo->SamplesPerTick)) * 100.0f;
                        if (inertiavel < 0.0f) { inertiavel = -inertiavel; };
            };

Add in Work, right below the for(..) statement:

            for( i=0; i<numsamples*2; i++ ) {
                        // New Inertia code
                        icnt++;
                        if (icnt >= 100) {
                                    icnt = 0;
                                    MaFiltah();
                        };

                        // Other stuff
                        inL = psamples[i];
                        inR = psamples[i+1];

Add to the DescribeValue function:

            case 2:
                        sprintf(txt,"%.2f ticks", ((float)value/500.0f) );
                        return txt;
                        break;

Here's the full source for the inertia filter:

  #include "../MachineInterface.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 paraInertia =
  {
            pt_word,                        // Parameter data type
            "Inertia",                        // Parameter name as its shown in the parameter 
                                                // window
            "Filter Inertia",// Parameter description as its shown in the 
                                                // pattern view's statusbar
            1,                                    // 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
            500                        // the default slider value
  };
  
  CMachineParameter const *pParameters[] = {
            &paraCutoff,
            &paraResonance,
            &paraInertia
  };
  CMachineAttribute const *pAttributes[] = { NULL };

  #pragma pack(1)                        
  
  class gvals
  {
  public:
            word cutoff;
            byte resonance;
            word inertia;
  };
  
  #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
            3,                                    // 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 CMachineInterfaceEx { };
  
  class mi : public CMachineInterface
  {
  public:
            mi();
            virtual ~mi();
            virtual void Tick();
            virtual void Init(CMachineDataInput * const pi);
            virtual bool Work(float *psamples, int numsamples, int const mode);
            virtual bool WorkMonoToStereo(float *psamples, int numsamples, int const mode);
            virtual void Command(int const i);
            virtual void Save(CMachineDataOutput * const po);
            virtual char const *DescribeValue(int const param, int const value);
            virtual CMachineInterfaceEx *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
            
            // Inertia system
            float dynamic_cutoff, dynamic_resonance;
            float inertiavel, inertiatick;
            int icnt;
            
            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 (dynamic_cutoff > param_cutoff) {
                        dynamic_cutoff -= inertiavel;
                        if (dynamic_cutoff <= param_cutoff) dynamic_cutoff = param_cutoff;
            } else if (dynamic_cutoff < param_cutoff) {
                        dynamic_cutoff += inertiavel;
                        if (dynamic_cutoff >= param_cutoff) dynamic_cutoff = param_cutoff;
            };
            if (dynamic_resonance > param_resonance) {
                        dynamic_resonance -= inertiavel;
                        if (dynamic_resonance <= param_resonance) dynamic_resonance = param_resonance;
            } else if (dynamic_resonance < param_resonance) {
                        dynamic_resonance += inertiavel;
                        if (dynamic_resonance >= param_resonance) dynamic_resonance = param_resonance;
            };

            if (dynamic_cutoff < 20.0f) { dynamic_cutoff = 20.0f; };
            if (dynamic_cutoff > 22000.0f) { dynamic_cutoff = 22000.0f; };
            if (dynamic_resonance < 1.0f) { dynamic_resonance = 1.0f; };
            if (dynamic_resonance > 127.0f) { dynamic_resonance = 127.0f; };

            omega = 2.0f * PI * dynamic_cutoff/pMasterInfo->SamplesPerSec;
            sn = sin (omega); cs = cos (omega);
            alpha = sn / dynamic_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::Init(CMachineDataInput * const pi)
  {
            pCB->SetnumOutputChannels(pCB->GetThisMachine(), 2);
            pCB->SetMachineInterfaceEx((CMachineInterfaceEx *)&ex);
            param_cutoff = 22050;
            param_resonance = 0;
            dynamic_cutoff = 22050;
            dynamic_resonance = 0;

            inertiavel = (22050.0f / pMasterInfo->SamplesPerSec) * 100.0f;
            inertiatick = 500.0f;

            lx1 = lx2 = ly1 = ly2 = 0.0f;
            rx1 = rx2 = ry1 = ry2 = 0.0f;
  }
  
  void mi::Save(CMachineDataOutput * const po) { }
  
  void mi::Tick() {
            if (gval.inertia != 0xFFFF) {
                        inertiavel = (float)((param_cutoff - dynamic_cutoff)/(((float)gval.inertia/500.0f) * pMasterInfo->SamplesPerTick)) * 100.0f;
                        if (inertiavel < 0.0f) { inertiavel = -inertiavel; };
                        inertiatick = gval.inertia;
            };
            if (gval.cutoff != 0xFFFF) {
                        param_cutoff = (float)gval.cutoff;
                        inertiavel = (float)((param_cutoff - dynamic_cutoff)/((inertiatick/500.0f) * pMasterInfo->SamplesPerTick)) * 100.0f;
                        if (inertiavel < 0.0f) { inertiavel = -inertiavel; };
            };
            if (gval.resonance != 0xFF) { 
                        param_resonance = ((float)gval.resonance / 2.0f); 
                        inertiavel = (float)((param_resonance - dynamic_resonance)/((inertiatick/500.0f) * pMasterInfo->SamplesPerTick)) * 100.0f;
                        if (inertiavel < 0.0f) { inertiavel = -inertiavel; };
            };
  }
  
  bool mi::Work(float *psamples, int numsamples, int const mode)
  {
            return false;
  }
  
  bool mi::WorkMonoToStereo(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++ ) {
                        icnt++;
                        if (icnt >= 100) {
                                    icnt = 0;
                                    MaFiltah();
                        };

                        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 +
                                    filtCoefTab[1] * rx1 +
                                    filtCoefTab[2] * rx2 +
                                    filtCoefTab[3] * ry1 +
                                    filtCoefTab[4] * ry2;
                        ry2 = ry1; ry1 = temp_y; rx2 = rx1; rx1 = outR ; outR = temp_y;

                        psamples[i] = outL;
                        i++;
                        psamples[i] = outR;
            };
            return true;
  }

  void mi::Command(int const i)
  {
            switch (i)
            {
            case 0:
                        MessageBox(NULL,"PFilter 1.0\n\nThis is an example created from a buzzdev effects tutorial written by CyanPhase","About PFilter",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:
                        sprintf(txt,"%.1f", (float)value );
                        return txt;
                        break;
            case 1:
                        sprintf(txt,"%.1f%%", ((float)value / 254.0f * 100.0f) );
                        return txt;
                        break;
            case 2:
                        sprintf(txt,"%.2f ticks", ((float)value/500.0f) );
                        return txt;
                        break;

            default:
                        return NULL;
            }
  }
  
  #pragma optimize ("", on) 
  
  DLL_EXPORTS

Convolver effect

Kibibu has released this Buzz effect called a convolver. Most people weren't exactly sure how it worked. After a bit of looking at the oscilloscopes, the freq view and so on, it sorta shows this is actually a sort of a basic FIR filter. It does spectral convolution (it changes the volume of different frequencies).

For practice, heres the WorkMonoToStereo code to make one :)

 bool mi::WorkMonoToStereo(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, inoutL, inoutR;
           int            i;
           for( i=0; i<numsamples*2; i++ ) {
                       inL = psamples[i];
                       inR = psamples[i+1];
                       inoutL = inL * x6amp + lx1 * x0amp + lx2 * x1amp +
                                                lx3 * x2amp + lx4 * x3amp + lx5 * x4amp + 
                                                lx6 * x5amp;
                       lx6 = lx5; lx5 = lx4; lx4 = lx3; 
                       lx3 = lx2; lx2 = lx1; lx1 = inL;
                       inoutR = inR * x6amp + rx1 * x0amp + rx2 * x1amp + 
                                                rx3 * x2amp + rx4 * x3amp + rx5 * x4amp + 
                                                rx6 * x5amp;
                       rx6 = rx5; rx5 = rx4; rx4 = rx3; 
                       rx3 = rx2; rx2 = rx1; rx1 = inR;
                       psamples[i] = inoutL;
                       i++;
                       psamples[i] = inoutR;
           };
           return true;
 }

These vars should go in the mi class

           float lx1, lx2, lx3, lx4, lx5, lx6;
           float rx1, rx2, rx3, rx4, rx5, rx6;
           float x0amp, x1amp, x2amp, x3amp, x4amp, x5amp, x6amp;

the amp variables are directly mapped to a bunch of parameters like this:

           if (gval.cvolve6 != 0xFFFF) x6amp = (gval.cvolve6 - 16384.0f) / 16384.0f;
           if (gval.cvolve5 != 0xFFFF) x5amp = (gval.cvolve5 - 16384.0f) / 16384.0f;
           .
           .

where the convolver params look like this:

           CMachineParameter const paraPolar6 = {
                       pt_word, "Polar Bear -6", "Polar Bear -6", 
                       0, 0x8000, 0xFFFF, MPF_STATE, 0x4000 
           };
           CMachineParameter const paraPolar5 = { 
                       pt_word, "Polar Bear -5", "Polar Bear -5", 
                       0, 0x8000, 0xFFFF, MPF_STATE, 0x4000 
           };
           .
           .

This should be able to get you started on making a convolver effect :)

A ring modulator

Making a ring modulator isn't too hard if you just want to create one with a decent sine wave. which we'll do here.

One thing that becomes a problem with using something like sin() is speed. it's hard to have speed and sin together. And since we're just creating something using sine waves, we don't need to go through a lot with wavetables and such. instead, we'll use a fast sine routine with a few coefs.

It's pretty simple because you basically just need to plug in the wanted frequency in the coef calculations :). The fast sine thing is demonstrated in one of my machines, DTMF-1, which the source is included (go get it just for the source).

Create a effect, "PRing", which has one parameter: frequency

Use some variables, but do make floats for ringcoeff, ringvalue1, and ringvalue2. make sure to initiate them to 0 in init function.

Whenever frequency changes, use the following:

            f = gval.frequency * (PI * 2)/pMasterInfo->SamplesPerSec;
            ringcoeff = 2.0f * cos(f);
            ringvalue1 = sin(0.0f);
            ringvalue2 = sin(-f + 0.0f);

In work, use this excerpt (not complete) to ring modulate the in signal with the sine wave.

            ringmodvalue = 1.0f * ringvalue1;
            temp = tone1value1;
            ringvalue1 = ringcoeff * ringvalue1 - ringvalue2;
            ringvalue2 = temp;

            inL = psamples[i];
            inR = psamples[i+1];

            outL = inL * ringmodvalue;
            outR = inR * ringmodvalue;

ringmodvalue and temp are local floats.

DC removal

If DC is a concern with your effects (it's mostly a concern with distortions and amplitude detection based effects). there are a few methods to get rid of it, one way is to take the RBJ cookbook HighPass filter and set it at a low resonance with the lowest cutoff.

Another solution is to use a simple DC filter thats used often in physical models (which tend to have the most problems with a DC offset because of the nature of very tiny delay lines):

in work:

           DCout = sample - DCin + (0.99f * DCout);
           DCin = sample;
           sample = DCout;

in mi class:

           float DCin, DCout;


Better about dialogs

If you want better than that crumby MessageBox() call, heres a small neat way of doing it:

Create a Resource Script file (if you don't have one already for skins) and make a dialog in it. Name its ID "IDD_MACABOUT" or something like that, and the dialog properties should be something like:

General tab:

           Has a caption like "About MyMachineNameHere"
           A decent font, you can change it if you want, or leave it as is.

Styles tab:

           Style: Popup
           Border: Dialog Frame
           Title bar and System menu are checked

More Styles tab:

           Visible is checked.

Put a OK button also, put a button somewhere, and Name its ID "IDOK".

Draw up the rest of the window at your likings...

Now the code: Add to the top of your machine this include:

 #include "resource.h"

Add near the end, but before mi::Command :

 HINSTANCE dllInstance;
 mi *g_mi;
 BOOL WINAPI DllMain ( HANDLE hModule, DWORD fwdreason, LPVOID lpReserved )
 {
  switch (fwdreason) {
  case DLL_PROCESS_ATTACH:
     dllInstance = (HINSTANCE) hModule;
     break;
  case DLL_THREAD_ATTACH: break;
  case DLL_THREAD_DETACH: break;
  case DLL_PROCESS_DETACH: break;
  }
  return TRUE;
 }
 BOOL APIENTRY AboutDialog(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
  switch(uMsg) {
  case WM_INITDIALOG: 
     return 1;
  case WM_SHOWWINDOW: 
     return 1;
  case WM_CLOSE:
     EndDialog (hDlg, TRUE);
     return 0;
  case WM_COMMAND:
     switch (LOWORD (wParam))
     {
     case IDOK:
        EndDialog(hDlg, TRUE);
        return 1;
     default:
        return 0;
     }
     break;
  }
  return 0;
 }


and add this call to mi::Command, where your About command is :)

           g_mi=this;
           DialogBox(dllInstance, MAKEINTRESOURCE (IDD_MACABOUT), GetForegroundWindow(), (DLGPROC) &AboutDialog);


About box with scrollable text box

However, what if you want a little bit more, like a about box with a small text box ala Matilde or Sea Cucumber? ;) it's more or less pretty simple. Just not too obvious.

In your resource dialog box's design view, add a Edit box, make it pretty nice and as big as it needs to be :). You might want to give it's ID something like "IDC_ABOUTTEXT", or whatever, you need to know the ID to reference tho in code later.

In the style tab section:

 Multiline is checked
 Vertical is checked
 AutoHScroll is NOT checked (so it can word wrap).
 Read only is checked
 

If you want a thinner border like the sea cucumber effect, simply uncheck Border, and in the Extended Style tab, check Static Edge.

In your code, you need to add some code when the WM_INITDIALOG message is sent. because you can't edit that in the resource editor:

   SetDlgItemText (hDlg, IDC_ABOUTTEXT, [a text string variable or constant here]);

Note that line breaks need to use "\r\n" and not just "\n". Otherwise you'll get little trippy squares all over your beautiful text box ;).

Remember to add the resource include to the top of your machine:

  1. include "resource.h"

Here's how that AboutDialog code could look like now:

 BOOL APIENTRY AboutDialog(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
  char abouttxt[10000];  // make that array as big as needed
  switch(uMsg) {
  case WM_INITDIALOG: 
     sprintf(abouttxt,"This is my buzz machine\r\n");
     sprintf(abouttxt,"%sOme SomeEffect\r\n",abouttxt);
     sprintf(abouttxt,"%sversion 1.0\r\n",abouttxt);
     sprintf(abouttxt,"%s\r\n",abouttxt);
     sprintf(abouttxt,"%sShouts to the following:\r\n",abouttxt);
     sprintf(abouttxt,"%syay\r\n",abouttxt);
     sprintf(abouttxt,"%s.\r\n",abouttxt);
     sprintf(abouttxt,"%s.\r\n",abouttxt);
     sprintf(abouttxt,"%s.\r\n",abouttxt);
     sprintf(abouttxt,"%s\r\n",abouttxt);
     sprintf(abouttxt,"%sCya\r\n",abouttxt);
     SetDlgItemText (hDlg, IDC_ABOUTTEXT, abouttxt);
     return 1;
  case WM_SHOWWINDOW: 
     return 1;
  case WM_CLOSE:
     EndDialog (hDlg, TRUE);
     return 0;
  case WM_COMMAND:
     switch (LOWORD (wParam))
     {
     case IDOK:
        EndDialog(hDlg, TRUE);
        return 1;
     default:
        return 0;
     }
     break;
  }
  return 0;
 }


Submenus on machine right click

instead of "Command 1\nCommand 2", put "/Command 1\nCommand 2" to have command 1 have that ">" arrow on the right. To populate it with submenus, handle this function:

 void miex::GetSubMenu(int const i, CMachineDataOutput *pout)

i starts counting from 0, i=0 is the first menu (Command 1), if Command 2 was /Command 2 instead, it would be i = 1. To add submenus it would look like this:

 void miex::GetSubMenu(int const i, CMachineDataOutput *pout) {
           switch (i) {
           case 0:
                       pout->Write ("SubItem 1 of Command 1");
                       pout->Write ("SubItem 2 of Command 1");
                       pout->Write ("SubItem 3 of Command 1");
                       pout->Write ("SubItem 4 of Command 1");
                       pout->Write ("SubItem 5 of Command 1");
                       pout->Write ("SubItem 6 of Command 1");
                       break;
           case 1:
                       pout->Write ("SubItem 1 of Command 2");
                       pout->Write ("SubItem 2 of Command 2");
                       pout->Write ("SubItem 3 of Command 2");
                       pout->Write ("SubItem 4 of Command 2");
                       pout->Write ("SubItem 5 of Command 2");
                       pout->Write ("SubItem 6 of Command 2");
                       break;
           case 2:
                       pout->Write ("SubItem 1 of Command 3");
                       pout->Write ("SubItem 2 of Command 3");
                       pout->Write ("SubItem 3 of Command 3");
                       pout->Write ("SubItem 4 of Command 3");
                       pout->Write ("SubItem 5 of Command 3");
                       pout->Write ("SubItem 6 of Command 3");
                       break;
           }
 };

To implement this function you need to declare this before the mi class:

 class miex : public CMachineInterfaceEx {
 public:
           virtual void GetSubMenu(int const i, CMachineDataOutput *pout);
 };

When a submenu is clicked, it calls mi::Command like a normal menu item. however, increments of 256 are also added: submenu_i = (i + 256) // for the first group of submenus submenu_i = (i + 512) // for the second group of submenus submenu_i = (i + 1024) // for the third group of submenus . (etc) .

so in mi::Command it would be similar to this:

 void mi::Command(int const i)
 {
           if (i < 256) {
                       // Main Menu
                       switch (i)
                       {
                       case 3: // Command 4 (which isn't a submenu thing)
                       ..
                       default:
                                   break;
                       };
           } else if ((i > 255) && (i < 512)) {
                       // Submenus of Command 1
                       return;
           } else if ((i > 511) && (i < 1024)) {
                       // Submenus of Command 2
                       return;
           } else if ((i > 1023) && (i < 1280)) {
                       // Submenus of Command 3
                       return;
           }
 }


Using non-modal dialogs

Managing non-modal windows in a buzz machine is not as obvious and simple as using a modal window (you need more than that little mi *g_mi to keep track of the associated machine), but it is often a better choice, as a modal window will render Buzz' user interface frozen while open.

Attached to this article (appendix B) is a set of classes and functions assembled by Mikko Apo to make loading those resource dialogs pretty easy and manageable, it is almost already placeable immediately in your machine code. simply follow the brief instructions in the comments (such as placing code into mi::mi and mi::~mi, etc.)

Remember to add the resource include as well to the top of your machine:

 #include "resource.h"

It may take some time to get familiar with the code, heres some explanations:

 if(findMiList(this)->windowSound==NULL)
 {
           findMiList(this)->windowSound = 
           CreateDialog(dllInstance, 
           MAKEINTRESOURCE(IDD_MACSOUNDINFO),
           GetForegroundWindow(),
           (DLGPROC) &SoundDialog);
 }

findMiList is a list of active machine interfaces (remember that when there are multiple instances of your machine, they all share the same space, the same dll, etc.) which is designed to associate a mi with a HWND.

your mi needs to have at least a HWND variable that references the created window, this is what "windowSound" is, which looks like this in the mi class body:

 .
 .
 public:
   HWND windowSound;
 public:
   gvals gval;
   avals aval;
 .
 .

CreateDialog is pretty straight forward, it looks similar to the DialogBox() function but always the dialog to be loaded without being shown, and show it later as non-modal. it behaves almost like a normal dialog as well.

&SoundDialog is the address of the function that makes up your dialog's message routine (pretty much the same as using the DialogBox one).

NOTE: make sure you dont try to call the pmi (callback to the machine) during WM_CREATE.


Some important guidelines when creating a dialog resource that won't screw up (some of it is not obvious):

  • its better to keep the windowstyle property to "dialog"
  • don't try to use "child window"

The rest of the options can be pretty much freely set (such as using toolwindow style windows, etc.)


Working with dialog user interface elements

Creating more complex dialogs with text fields isnt very hard. just a matter of using sprintf() and functions like atoi. the methods are shown below by "user interface and usage" category:

GetDlgItemText (hDlg, IDC_WINDOWMAXH, buffertxt, 128);

Text Field that keeps a string

To write to the box:

    SetDlgItemText (hDlg, IDC_EDIT1, pmi->thestring);

To read from the box:

    char buffertxt[128];
    GetDlgItemText (hDlg, IDC_EDIT1, buffertxt, 128);
    sprintf(pmi->thestring, buffertxt);

Text Field that uses integers

to write to the box:

    char buffertxt[128];
    sprintf(buffertxt, "%i", pmi->theinteger);
    SetDlgItemText (hDlg, IDC_EDIT1, buffertxt);

To read from the box:

    char buffertxt[128];
    GetDlgItemText (hDlg, IDC_EDIT1, buffertxt, 128);
    pmi->theinteger = (int)atoi(buffertxt);

NOTE: you can set the text box's "Number" flag in the resource dialog and the box will only accept numbers.

Text Field that uses floats

    // to write to the box:
    char buffertxt[128];
    sprintf(buffertxt, "%f", pmi->thefloat);
    SetDlgItemText (hDlg, IDC_EDIT1, buffertxt);
    
    // to read from the box:
    char buffertxt[128];
    GetDlgItemText (hDlg, IDC_EDIT1, buffertxt, 128);
    pmi->thefloat = (int)atof(buffertxt);

A drop down list combo box

    // to populate the combo box list:
    SendMessage(GetDlgItem(hDlg, IDC_COMBO1), CB_RESETCONTENT, 0, 0);
    SendMessage(GetDlgItem(hDlg, IDC_COMBO1), CB_ADDSTRING, 0, (LPARAM)(LPCTSTR)("White Noise"));
    SendMessage(GetDlgItem(hDlg, IDC_COMBO1), CB_ADDSTRING, 0, (LPARAM)(LPCTSTR)("Pink Noise"));
    SendMessage(GetDlgItem(hDlg, IDC_COMBO1), CB_ADDSTRING, 0, (LPARAM)(LPCTSTR)("Brown Noise"));
    SendMessage(GetDlgItem(hDlg, IDC_COMBO1), CB_ADDSTRING, 0, (LPARAM)(LPCTSTR)("Orange Noise"));
    SendMessage(GetDlgItem(hDlg, IDC_COMBO1), CB_ADDSTRING, 0, (LPARAM)(LPCTSTR)("Blue Noise"));
    SendMessage(GetDlgItem(hDlg, IDC_COMBO1), CB_ADDSTRING, 0, (LPARAM)(LPCTSTR)("Green Noise"));
    SendMessage(GetDlgItem(hDlg, IDC_COMBO1), CB_ADDSTRING, 0, (LPARAM)(LPCTSTR)("Black Noise"));
    
    // to set the selected entry:
    SendMessage(GetDlgItem(hDlg, IDC_COMBO1), CB_SETCURSEL, (int)(pmi->thecolorsetting), 0);
    
    // to read the selected entry:
    pmi->thecolorsetting = (int)SendMessage(GetDlgItem(hDlg,IDC_COMBO1), CB_GETCURSEL, 0, 0);

NOTE: make sure the messages are CB_, LB_ don't work

A simple list

    // to populate the combo box list:
    SendMessage(GetDlgItem(hDlg, IDC_COMBO1), LB_RESETCONTENT, 0, 0);
    SendMessage(GetDlgItem(hDlg, IDC_COMBO1), LB_ADDSTRING, 0, (LPARAM)(LPCTSTR)("White Noise"));
    SendMessage(GetDlgItem(hDlg, IDC_COMBO1), LB_ADDSTRING, 0, (LPARAM)(LPCTSTR)("Pink Noise"));
    SendMessage(GetDlgItem(hDlg, IDC_COMBO1), LB_ADDSTRING, 0, (LPARAM)(LPCTSTR)("Brown Noise"));
    SendMessage(GetDlgItem(hDlg, IDC_COMBO1), LB_ADDSTRING, 0, (LPARAM)(LPCTSTR)("Orange Noise"));
    SendMessage(GetDlgItem(hDlg, IDC_COMBO1), LB_ADDSTRING, 0, (LPARAM)(LPCTSTR)("Blue Noise"));
    SendMessage(GetDlgItem(hDlg, IDC_COMBO1), LB_ADDSTRING, 0, (LPARAM)(LPCTSTR)("Green Noise"));
    SendMessage(GetDlgItem(hDlg, IDC_COMBO1), LB_ADDSTRING, 0, (LPARAM)(LPCTSTR)("Black Noise"));
    
    // to set the selected entry:
    SendMessage(GetDlgItem(hDlg, IDC_COMBO1), LB_SETCURSEL, (int)(pmi->thecolorsetting), 0);
    
    // to read the selected entry:
    pmi->thecolorsetting = (int)SendMessage(GetDlgItem(hDlg,IDC_COMBO1), LB_GETCURSEL, 0, 0);

NOTE: make sure the messages are LB_, CB_ don't work

Buttons

To recieve clicks:

    case WM_COMMAND: {
        switch ( LOWORD (wParam)) {
        case IDC_BUTTON: 
        {   // just like IDOK and stuff
            // do stuff
            return 1;
        }

Adding attributes and cautions with updates

Adding attributes to a buzz machine is not hard:

Add something like this:

  CMachineAttribute const attrFormant1 = {
            "PhM: Formant 1",
            1,     // min
            5000,  // max
            1000   // defaultvalue
  };

And then change pAttributes to look like this:

  CMachineAttribute const *pAttributes[] = {
            &attrFormant1
  };

Don't forget to increment the number of attributes in the machine info class.

Make sure you have a avals class:

  class avals
  {
  public:
            int formant1;
  };

NOTE: all attributes are int

In the mi::mi() constructor, make sure there is:

    .
    .
    AttrVals = (int *)&aval;

To access the values at any time, just use aval.attributename like:

  .
  .
  Filter(aval.formant1,psamples,numsamples);
  .
  .
  etc.

There is also a function that notifies when the attributes change

  void AttributesChanged() {
  .
  .
  }

Guidelines when updating:

  • Do not touch the parameters, don't change the min, max, default, or any of the parameter names or descriptions.
  • Do not change the order, add, or remove parameters.
  • When saving and loading from Init and Save, make sure to do versioning of the data, be backwards compatible with older stored custom data, don't overread or underread the data either.
  • Adding attributes and menus seems to be the only safe way of doing it. Changing the DescribeValue function is also safe.
  • Always test with a bmx that was used with older versions of the machine don't blindly release them without testing with old bmxs :).
  • If you really need to change the name of a parameter, it's safer to do it through miex::DescribeParam.

Saving and loading custom data

Saving and loading custom data is not too hard to do, just remember that the data can only be retrieved during Init, and saved during Save.

  void mi::Init(CMachineDataInput * const pi)
  {
            byte TheVersion;

            int i = 0;
            unsigned int u = 0;
            pCB->SetnumOutputChannels(pCB->GetThisMachine(), 2);
            pCB->SetMachineInterfaceEx((CMachineInterfaceEx *)&ex);
            ThisMachine = pCB->GetThisMachine();
            ex.pmi = this;

        // loading begins here
            if (pi != NULL) {
                        pi->Read (TheVersion);  // you do your own versioning

                        pi->Read (wave_rootnote);      // int wave_rootnote;
                        pi->Read (wave_title,256);     // char wave_title[256];
                        pi->Read (wave_filepath,256);  // char wave_filepath[256];
            }
  }
  
  void mi::Save(CMachineDataOutput * const po) {
            byte TheVersion = 1;
            int i = 0;

            po->Write(TheVersion);

            pi->Write (wave_rootnote);      // int wave_rootnote;
            pi->Write (wave_title,256);     // char wave_title[256];
            pi->Write (wave_filepath,256);  // char wave_filepath[256];
  }

Remember that variables like int and float are easily storeable and retrievable, the function know how large they are and you won't need to say anything more to it.

As for strings, you'll need to specify how many bytes (characters) your string holds.

Adding stereo entries in the wavetable

This is how to add a mono wavetable entry with a recorder-like machine:

  // Mono recording
  currentnumofsamples = 5 * pCB->SamplesPerTick; // 5 Tick long wavetable entry
  if (pCB->AllocateWave(waveinuse, currentnumofsamples, "Untitled Mono") == true) {
            recordnow = 1;
  } else {
            recordnow = 0;
  };

However, adding stereo entries to the wavetable is not as obvious as with mono entries, because you weren't exactly allowed to set the flag for the wavetable. that's easily fixeable by forcing the value on the wave's flags and numsamples.

  // Stereo recording
  currentnumofsamples = 5 * pCB->SamplesPerTick; // 5 Tick long wavetable entry
  if (pCB->AllocateWave(waveinuse, currentnumofsamples * 2, "Untitled Stereo") == true) {
            currentnumofsamples *= 2; // theres twice as many samples

            // This forces the sample to be considered stereo
            CWaveInfo const *mewav = pCB->GetWave(waveinuse);
            int *mewavefl = (int *)&mewav->Flags;
            *mewavefl = *mewavefl | WF_STEREO;

            // This forces the sample's numsample and LoopEnd 
            // to a correct value
            CWaveLevel const *mewav2 = pCB->GetWaveLevel(waveinuse,0);
            int *mewavens = (int *)&mewav2->numSamples;
            *mewavens = currentnumofsamples / 2;
            int *mewavele = (int *)&mewav2->LoopEnd;
            *mewavele = currentnumofsamples / 2;
  
            recordnow = 1;
  } else {
            recordnow = 0;
  };

Note

make sure you initialize with AllocateWave() with the amount of samples needed (twice as much), however you have to force a change on it's numsamples to half what was with AllocateWave(). If this is not done then most of the buzz sample trackers will just crash as they read right through the last sample instead of stopping\looping.

A. Atomstereomeld source code

  // CyanPhase AtomStereoMeld Effect
  
  #include "../MachineInterface.h"
  #include <windows.h>
  
  #pragma optimize ("awy", on) 

  CMachineParameter const paraPolarityType =
  {
            pt_byte,
            "PolarityType",
            "PolarityType",
            0,
            3,
            0xFF,
            MPF_STATE,
            0
  };
  
  CMachineParameter const paraMeld =
  {
            pt_byte,
            "Meld",
            "Stereo Meld",
            0,
            0xFE,
            0xFF,
            MPF_STATE,
            127
  };
  
  CMachineParameter const paraLeftGain =
  {
            pt_byte,
            "Gain-Left",
            "Left Gain",
            0,
            0xFE,
            0xFF,
            MPF_STATE,
            0xFE
  };
  
  CMachineParameter const paraMiddleGain =
  {
            pt_byte,
            "Gain-Center",
            "Center Gain",
            0,
            0xFE,
            0xFF,
            MPF_STATE,
            0xFE
  };
  
  CMachineParameter const paraRightGain =
  {
            pt_byte,
            "Gain-Right",
            "Right Gain",
            0,
            0xFE,
            0xFF,
            MPF_STATE,
            0xFE
  };
  
  CMachineParameter const *pParameters[] = { &paraPolarityType, &paraMeld, &paraLeftGain, &paraMiddleGain, &paraRightGain};
  CMachineAttribute const *pAttributes[] = { NULL, };
  
  #pragma pack(1)                        
  
  class gvals
  {
  public:
            byte type;
            byte mix;
            byte leftgain;
            byte middlegain;
            byte rightgain;
  };
  
  #pragma pack()
  
  CMachineInfo const MacInfo = 
  {
            MT_EFFECT,
            MI_VERSION,            
            MIF_DOES_INPUT_MIXING,
            0,                                                            // min tracks
            0,                                                            // max tracks
            5,                                                            // numGlobalParameters
            0,                                                            // numTrackParameters
            pParameters,
            0,
            pAttributes,
            "CyanPhase AtomStereoMeld",                        // name
            "Stereo Meld",                                                // short name
            "Edward L. Blake",                                    // author
            "&About..."
  };
  
  class miex : public CMachineInterfaceEx { };
  
  class mi : public CMachineInterface
  {
  public:
            mi();
            virtual ~mi();
  
            virtual void Tick();
  
            virtual void Init(CMachineDataInput * const pi);
            virtual bool Work(float *psamples, int numsamples, int const mode);
            virtual bool WorkMonoToStereo(float *psamples, int numsamples, int const mode);
            virtual void Command(int const i);
  
            virtual void Save(CMachineDataOutput * const po);
  
            virtual char const *DescribeValue(int const param, int const value);
  
  public:
            virtual CMachineInterfaceEx *GetEx() { return &ex; }
            virtual void OutputModeChanged(bool stereo) {}
  
  public:
            miex ex;
  
  public:
            float leftgain, middlegain, rightgain;
            float valve, antivalve;

            int type;

            gvals gval;

  };
  
  mi::mi() {  GlobalVals = &gval; }
  mi::~mi() { }
  
  void mi::Init(CMachineDataInput * const pi)
  {
            pCB->SetnumOutputChannels(pCB->GetThisMachine(), 2);
            pCB->SetMachineInterfaceEx((CMachineInterfaceEx *)&ex);
            leftgain = 1.0f;
            middlegain = 1.0f;
            rightgain = 1.0f;

            valve = (254.0f - 127.0f)/254.0f;
            antivalve = (127.0f)/254.0f;

            type = 0;
  }
  
  void mi::Save(CMachineDataOutput * const po) { }
  
  void mi::Tick() {
            unsigned int f = 0;

            if (gval.mix != 0xFF) {
                        f = gval.mix;
                        valve = (254.0f - f)/254.0f;
                        antivalve = (f)/254.0f;            
            };

            if (gval.type != 0xFF) {
                        type = gval.type;
            };

            if (gval.leftgain != 0xFF) {
                        leftgain = (gval.leftgain / 254.0f);
            };

            if (gval.middlegain != 0xFF) {
                        middlegain = (gval.middlegain / 254.0f);
            };

            if (gval.rightgain != 0xFF) {
                        rightgain = (gval.rightgain / 254.0f);
            };

  }
  
  bool mi::Work(float *psamples, int numsamples, int const mode)
  {
            return false;
  }

  bool mi::WorkMonoToStereo(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, leftb, midb, rightb;
            int            i;

            switch (type) {
            case 0: // +L +R
                        for( i=0; i<numsamples*2; i++ ) {

                                    inL = psamples[i];
                                    inR = psamples[i+1];


                                    leftb = (valve*inL) * leftgain;
                                    midb = ((antivalve * inL) + (antivalve * inR)) * middlegain;
                                    rightb = (valve*inR) * rightgain;

                                    psamples[i] = leftb + midb;
                                    i++;
                                    psamples[i] = rightb + midb;
                        };
                        break;
            case 1: // +L -R
                        for( i=0; i<numsamples*2; i++ ) {

                                    inL = psamples[i];
                                    inR = -(psamples[i+1]);


                                    leftb = (valve*inL) * leftgain;
                                    midb = ((antivalve * inL) + antivalve * inR) * middlegain;
                                    rightb = (valve*inR) * rightgain;

                                    psamples[i] = leftb + midb;
                                    i++;
                                    psamples[i] = rightb + midb;
                        };
                        break;
            case 2: // -L +R
                        for( i=0; i<numsamples*2; i++ ) {

                                    inL = -(psamples[i]);
                                    inR = psamples[i+1];


                                    leftb = (valve*inL) * leftgain;
                                    midb = ((antivalve * inL) + antivalve * inR) * middlegain;
                                    rightb = (valve*inR) * rightgain;

                                    psamples[i] = leftb + midb;
                                    i++;
                                    psamples[i] = rightb + midb;
                        };
                        break;
            case 3: // -L -R
                        for( i=0; i<numsamples*2; i++ ) {

                                    inL = -(psamples[i]);
                                    inR = -(psamples[i+1]);


                                    leftb = (valve*inL) * leftgain;
                                    midb = ((antivalve * inL) + antivalve * inR) * middlegain;
                                    rightb = (valve*inR) * rightgain;

                                    psamples[i] = leftb + midb;
                                    i++;
                                    psamples[i] = rightb + midb;
                        };
                        break;
            default:
                        break;
            }
            return true;
  }

  void mi::Command(int const i)
  {
            switch (i)
            {
            case 0:
                        MessageBox(NULL,"CyanPhase AtomStereoMeld 1.0\n\nCopyright 2000 Edward L. Blake\nEmail: blakee@rovoscape.com","About CyanPhase AtomStereoMeld",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:
                        switch (value) {
                        case 0: return "+L +R"; break;
                        case 1: return "+L -R"; break;
                        case 2: return "-L +R"; break;
                        case 3: return "-L -R"; break;
                        default: return NULL; break;
                        }
                        break;
            case 1:
                        sprintf(txt,"%.1f%%", value/254.0f*100.0f );
                        return txt;
                        break;

            case 2:
                        sprintf(txt,"%.1f%%", value/254.0f*100.0f );
                        return txt;
                        break;
            case 3:
                        sprintf(txt,"%.1f%%", value/254.0f*200.0f );
                        return txt;
                        break;
            case 4:
                        sprintf(txt,"%.1f%%", value/254.0f*100.0f );
                        return txt;
                        break;
  
            default:
                        return NULL;
            }
  }
  
  #pragma optimize ("", on) 
  
  DLL_EXPORTS

B. classes for multiple non-modal dialogs

  //
  // multi instance multi-window handling for buzz plugins by apo
  // uses ideas from Cyanphase and Zephod
  //
  
  // place these to the main .cpp, after the definition of mi (public CMachineInterface)
  // and they need to be before the functions that use them
  // also, place them outside of any class
  
  // GUI stuff from Cyanphase's guide
  HINSTANCE dllInstance;
  
  BOOL WINAPI DllMain ( HANDLE hModule, DWORD fwdreason, LPVOID lpReserved )
  {
   switch (fwdreason) {
   case DLL_PROCESS_ATTACH:
      dllInstance = (HINSTANCE) hModule;
      break;

   case DLL_THREAD_ATTACH: break;
   case DLL_THREAD_DETACH: break;
   case DLL_PROCESS_DETACH: break;
   }
   return TRUE;
  }
  
  // Cyanphase's stuff ends here
  
  // container for each mi's windows
  
  class MiList
  {
  public:
            mi *thisMI;
            HWND windowTiming,windowMidi,windowSound;            // three windows
  };
  
  
  static MiList *miList=NULL;
  static int miCount=0;
  
  // this function is used to find the related windows and mi instance for any window or mi
  // to find mi related to window use this code: findMiList(windowHWND)->thisMI;
  // if you have public variables/functions you can access them using the above
  MiList *findMiList(void *ptr)
  {
            int i=0; 
            while( i<miCount)
            {
                        if( miList[i].thisMI==ptr || miList[i].windowMidi==ptr || \
                                    miList[i].windowSound==ptr || miList[i].windowTiming==ptr ) 
                        {
                                    return &miList[i];
                        }
                        i++;
            }
            return NULL;
  }
  
  // handles the updating of the window, place outside any class.
  // you can make the window update itself from the class by calling 
  // updateSoundDialog(findMiList(void *ptr)->windowSound, this);
  
  void updateSoundDialog(HWND hDlg)
  {
            char txt[20000];
            // mi::print_soundinfo prints information to txt buffer
            findMiList(hDlg)->thisMI->print_soundinfo(txt);
            // txt buffer is printed to IDC_SOUNDINFO
            SetDlgItemText (hDlg, IDC_SOUNDINFO, txt);
            // a checkbox is set to the value of attribute autoupdatesound
            SendMessage(GetDlgItem(hDlg,IDC_AUTOUPDATECHECK), BM_SETCHECK, (WPARAM)(findMiList(hDlg)->thisMI->aval.autoupdatesound), 0L);
  }
  
  // window's message handler
  
  BOOL APIENTRY SoundDialog(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {

   switch(uMsg) {

   case WM_INITDIALOG: 
            // you can't use findMiList in INITDIALOG because CreateDialog hasn't returned yet
      return 1;

   case WM_SHOWWINDOW:
            // the window can't have the "Visible" flag set, if you use findMiList in SHOWWINDOW
            // otherwise CreateDialog sends SHOWWINDOW too and this will crash
               updateSoundDialog(hDlg);
      return 1;

   case WM_CLOSE:
            // clear the pointer from miList
              findMiList(hDlg)->windowSound=NULL;
      EndDialog (hDlg, TRUE);
              return 1;

   case WM_COMMAND:
      switch (LOWORD (wParam))
      {
      case IDCLOSE:
            // clear the pointer from miList
             findMiList(hDlg)->windowSound=NULL;
         EndDialog(hDlg, TRUE);
         return 1;
      case IDCUPDATE:
       // update button was pressed
             updateSoundDialog(hDlg);
             return 1;
      case IDCRESET:
       // reset button was pressed, this will call mi::resetSound()
               findMiList(hDlg)->thisMI->resetSound();
               return 1;
      case IDC_AUTOUPDATECHECK:
            // autoupdate checkbox was selected, this will update the aval.autoupdatesound from mi::aval
               findMiList(hDlg)->thisMI->aval.autoupdatesound= (int)SendMessage(GetDlgItem(hDlg,IDC_AUTOUPDATECHECK), BM_GETCHECK, 0, 0L);
               return 1;
      default:
         return 0;
      }
            break;

            default:
            break;
   }
   return 0;
  }
  
  // end of global stuff
  
  // add these changes to mi's constructor
  
  mi::mi()
  {

            // GUI related

            miCount++;
            miList = (MiList *) realloc( miList, miCount*sizeof(MiList) );
            MiList *tmp=&miList[miCount-1];
            tmp->thisMI = this;
            tmp->windowMidi=NULL;            // clear window pointers
            tmp->windowSound=NULL;
            tmp->windowTiming=NULL;
  }
  
  // add these changes to mi's destructor
  
  mi::~mi()
  {

            // GUI related
            int i=0;
            while( miList[i].thisMI!=this ) i++;            // find this instance from the list
            MiList *tmp=&miList[i];

            // close windows if they are still open
            if( tmp->windowMidi )            DestroyWindow(tmp->windowMidi);
            if( tmp->windowSound )            DestroyWindow(tmp->windowSound);
            if( tmp->windowTiming )            DestroyWindow(tmp->windowTiming);

            // copy the last in the list to the place of the removed

            tmp->thisMI = miList[--miCount].thisMI;
            tmp->windowMidi=miList[miCount].windowMidi;
            tmp->windowSound=miList[miCount].windowSound;
            tmp->windowTiming=miList[miCount].windowTiming;
  }

  // launch the window, place this code where you want to launch the window

  {
                        if(findMiList(this)->windowSound==NULL)
                        {
                                    findMiList(this)->windowSound=CreateDialog(dllInstance,MAKEINTRESOURCE(IDD_MACSOUNDINFO),GetForegroundWindow(),(DLGPROC) &SoundDialog);
                        }
                        if(findMiList(this)->windowSound)
                        {
                                    // if window doesn't have "Visible" flag we need to make it visible
                                    ShowWindow(findMiList(this)->windowSound,SW_SHOW);
                        }
  }

C. stereo to mono source code

  #include "../MachineInterface.h"
  #include <windows.h>

  #pragma optimize ("awy", on) 
  
  CMachineParameter const paraLeftGain =
  {
            pt_byte,
            "Gain-Left",
            "Left Gain",
            0,
            0xFE,
            0xFF,
            MPF_STATE,
            0xFE
  };
  
  CMachineParameter const paraRightGain =
  {
            pt_byte,
            "Gain-Right",
            "Right Gain",
            0,
            0xFE,
            0xFF,
            MPF_STATE,
            0xFE
  };
  
  CMachineParameter const *pParameters[] = { &paraLeftGain, &paraRightGain };
  CMachineAttribute const *pAttributes[] = { NULL };

  #pragma pack(1)                        
  
  class gvals
  {
  public:
            byte leftgain;
            byte rightgain;
  };
  
  #pragma pack()
  
  
  CMachineInfo const MacInfo = 
  {
            MT_EFFECT,
            MI_VERSION,            
            MIF_DOES_INPUT_MIXING,
            0,                                                                                                                        // min tracks
            0,                                                                                                                        // max tracks
            2,                                                                                                                        // numGlobalParameters
            0,                                                                                                                        // numTrackParameters
            pParameters,
            0,
            pAttributes,
            "CyanPhase Mono",                                                                                                // name
            "Mono",                                                                                                                                    // short name
            "Edward L. Blake",                                                                                                // author
            "&About..."
  };

  class mi;

  class miex : public CMachineInterfaceEx 
  {
  public:
            virtual void AddInput(char const *macname, bool stereo);            // called when input is added to a machine
            virtual void DeleteInput(char const *macename);                                    
            virtual void RenameInput(char const *macoldname, char const *macnewname); 
            virtual void SetInputChannels(char const *macname, bool stereo); //{}
            virtual void Input(float *psamples, int numsamples, float amp); // if MIX_DOES_INPUT_MIXING
            virtual bool HandleInput(int index, int amp, int pan); //{ return false; }
  public:

            mi *pmi;
  };
  
  class mi : public CMachineInterface
  {
  public:
            mi();
            virtual ~mi();
            virtual void Tick();
            virtual void Init(CMachineDataInput * const pi);
            virtual bool Work(float *psamples, int numsamples, int const mode);
            virtual bool WorkMonoToStereo(float *psamples, int numsamples, int const mode);
            virtual void Command(int const i);
            virtual void Save(CMachineDataOutput * const po);
            virtual char const *DescribeValue(int const param, int const value);

  public:
            virtual CMachineInterfaceEx *GetEx() { return &ex; }
            virtual void OutputModeChanged(bool stereo) {}
            
            float thearray[1024];
            float leftgain, rightgain;

  public:
            miex ex;

  public:

            gvals gval;

  };
  
  void miex::AddInput(char const *macname, bool stereo) { }
  void miex::DeleteInput(char const *macename) { }
  void miex::RenameInput(char const *macoldname, char const *macnewname) { }
  void miex::SetInputChannels(char const *macname, bool stereo) { }
  void miex::Input(float *psamples, int numsamples, float amp) {
            int i;
            for (i = 0; i < numsamples; i++) {
                        pmi->thearray[i] = pmi->thearray[i] + (((pmi->leftgain * *psamples++) + (pmi->rightgain * *psamples++)) / 2.0f) * amp;
            }
  }
  bool miex::HandleInput(int index, int amp, int pan) { return false; }
  
  mi::mi() {  GlobalVals = &gval; }
  mi::~mi() { }
  
  void mi::Init(CMachineDataInput * const pi)
  {
            pCB->SetnumOutputChannels(pCB->GetThisMachine(), 2);            //             Work will never be called, meaning that Buzz will convert a mono signal to
                                                                                    //            stereo itself and call WorkMonoToStereo insted.
                                                                                    //            If false, Work will be called in mono cases, and the output should be mono
            pCB->SetMachineInterfaceEx((CMachineInterfaceEx *)&ex);
            ex.pmi = this;
            leftgain = 1.0f;
            rightgain = 1.0f;
  }
  
  void mi::Save(CMachineDataOutput * const po) { }
  
  void mi::Tick() {
            if (gval.leftgain != 0xFF) {
                        leftgain = (gval.leftgain / 254.0f);
            };
            if (gval.rightgain != 0xFF) {
                        rightgain = (gval.rightgain / 254.0f);
            };
  }

  bool mi::Work(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;

            int i;
            for (i = 0; i < numsamples; i++) {
                        *psamples++ = thearray[i];
                        thearray[i] = 0.0f;
            }

            return true;
  }

  bool mi::WorkMonoToStereo(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;

            int i;
            for (i = 0; i < numsamples; i++) {
                        *psamples++ = thearray[i];
                        *psamples++ = thearray[i];
                        thearray[i] = 0.0f;
            }

            return true;
  }
  
  void mi::Command(int const i)
  {
            switch (i)
            {
            case 0:
                        MessageBox(NULL,"CyanPhase Mono 1.0\n\nCopyright 2000 Edward L. Blake\nEmail: blakee@rovoscape.com\n\nPretty damn easy machine that makes stereo go into mono conveniently","About CyanPhase Mono",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:
                        sprintf(txt,"%.1f%%", value/254.0f*100.0f );
                        return txt;
                        break;
            case 1:
                        sprintf(txt,"%.1f%%", value/254.0f*100.0f );
                        return txt;
                        break;

            default:
                        return NULL;
            }
  }
  
  #pragma optimize ("", on) 
  
  DLL_EXPORTS

D. Guru4 type filter source code

  // This shows basically the filter used as "Phatt LP" in 
  // Arguelles' Guru4 machine
  // Thx to: Arguru for telling me how it works
  
  #include "../MachineInterface.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 paraType =
  {
            pt_byte,                        // Parameter data type
            "Type",                        // Parameter name as its shown in the parameter 
                                                // window
            "Filter Type",// Parameter description as its shown in the 
                                                // pattern view's statusbar
            0,                                    // Minimum value
            0x1,                        // 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 paraInertia =
  {
            pt_word,                        // Parameter data type
            "Inertia",                        // Parameter name as its shown in the parameter 
                                                // window
            "Filter Inertia",// Parameter description as its shown in the 
                                                // pattern view's statusbar
            1,                                    // 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
            500                        // the default slider value
  };

  CMachineParameter const *pParameters[] = {
            &paraCutoff,
            &paraResonance,
            &paraType,
            &paraInertia
  };
  CMachineAttribute const *pAttributes[] = { NULL };
  
  #pragma pack(1)                        
  
  class gvals
  {
  public:
            word cutoff;
            byte resonance;
            byte filttype;
            word inertia;
  };
  
  #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
            4,                                    // numGlobalParameters
            0,                                    // numTrackParameters
            pParameters,            // pointer to parameter stuff
            0,                                    // numAttributes
            pAttributes,            // pointer to attribute stuff
            "GuruFilter",            // Full Machine Name
            "GuruFilter",                        // Short name
            "Reimported by CyanPhase",            // Author name
            "&About..."                        // Right click menu commands
  };
  
  class miex : public CMachineInterfaceEx { };
  
  class mi : public CMachineInterface
  {
  public:
            mi();
            virtual ~mi();
            virtual void Tick();
            virtual void Init(CMachineDataInput * const pi);
            virtual bool Work(float *psamples, int numsamples, int const mode);
            virtual bool WorkMonoToStereo(float *psamples, int numsamples, int const mode);
            virtual void Command(int const i);
            virtual void Save(CMachineDataOutput * const po);
            virtual char const *DescribeValue(int const param, int const value);
            virtual CMachineInterfaceEx *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 guru_fb, guru_q, guru_f;
            float guru_buf0L, guru_buf1L;
            float guru_buf0R, guru_buf1R;

            int filt_type;

            // Inertia system
            float dynamic_cutoff, dynamic_resonance;
            float inertiavel, inertiatick;
            int icnt;
            
            gvals gval;
  };
  
  mi::mi() {  GlobalVals = &gval; }
  mi::~mi() { }
  
  void mi::MaFiltah () {
            // These limits the cutoff frequency and resonance to
            // reasoneable values.

            if (dynamic_cutoff > param_cutoff) {
                        dynamic_cutoff -= inertiavel;
                        if (dynamic_cutoff <= param_cutoff) dynamic_cutoff = param_cutoff;
            } else if (dynamic_cutoff < param_cutoff) {
                        dynamic_cutoff += inertiavel;
                        if (dynamic_cutoff >= param_cutoff) dynamic_cutoff = param_cutoff;
            };
            if (dynamic_resonance > param_resonance) {
                        dynamic_resonance -= inertiavel;
                        if (dynamic_resonance <= param_resonance) dynamic_resonance = param_resonance;
            } else if (dynamic_resonance < param_resonance) {
                        dynamic_resonance += inertiavel;
                        if (dynamic_resonance >= param_resonance) dynamic_resonance = param_resonance;
            };

            if (dynamic_cutoff < 20.0f) { dynamic_cutoff = 20.0f; };
            if (dynamic_cutoff > 22000.0f) { dynamic_cutoff = 22000.0f; };
            if (dynamic_resonance < 1.0f) { dynamic_resonance = 1.0f; };
            if (dynamic_resonance > 127.0f) { dynamic_resonance = 127.0f; };

            guru_q = dynamic_resonance / 127.0f;
            guru_f = dynamic_cutoff / pMasterInfo->SamplesPerSec;

            //set feedback amount given f and q between 0 and 1
            guru_fb = guru_q + guru_q/(1.0f - guru_f);

  }
  
  void mi::Init(CMachineDataInput * const pi)
  {
            pCB->SetnumOutputChannels(pCB->GetThisMachine(), 2);
            pCB->SetMachineInterfaceEx((CMachineInterfaceEx *)&ex);
            param_cutoff = 22050;
            param_resonance = 0;
            dynamic_cutoff = 22050;
            dynamic_resonance = 0;

            inertiavel = (22050.0f / pMasterInfo->SamplesPerSec) * 100.0f;
            inertiatick = 500.0f;

            guru_fb = 0.0f;
            guru_q = 0.0f;
            guru_f = 0.0f;
            guru_buf0L = 0.0f;
            guru_buf1L = 0.0f;
            guru_buf0R = 0.0f;
            guru_buf1R = 0.0f;

            filt_type = 0;
  }
  
  void mi::Save(CMachineDataOutput * const po) { }
  
  void mi::Tick() {
            if (gval.inertia != 0xFFFF) {
                        inertiavel = (float)((param_cutoff - dynamic_cutoff)/(((float)gval.inertia/500.0f) * pMasterInfo->SamplesPerTick)) * 100.0f;
                        if (inertiavel < 0.0f) { inertiavel = -inertiavel; };
                        inertiatick = gval.inertia;
            };
            if (gval.cutoff != 0xFFFF) {
                        param_cutoff = (float)gval.cutoff;
                        inertiavel = (float)((param_cutoff - dynamic_cutoff)/((inertiatick/500.0f) * pMasterInfo->SamplesPerTick)) * 100.0f;
                        if (inertiavel < 0.0f) { inertiavel = -inertiavel; };
            };
            if (gval.resonance != 0xFF) { 
                        param_resonance = ((float)gval.resonance / 2.0f); 
                        inertiavel = (float)((param_resonance - dynamic_resonance)/((inertiatick/500.0f) * pMasterInfo->SamplesPerTick)) * 100.0f;
                        if (inertiavel < 0.0f) { inertiavel = -inertiavel; };
            };
            
            if (gval.filttype != 0xFF) { 
                        filt_type = gval.filttype; 
            };
  }
  
  bool mi::Work(float *psamples, int numsamples, int const mode)
  {
            return false;
  }
  
  bool mi::WorkMonoToStereo(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++ ) {
                        icnt++;
                        if (icnt >= 100) {
                                    icnt = 0;
  //                                    decay_amount += decay_over_time;
                                    MaFiltah();
                        };

                        inL = psamples[i];
                        inR = psamples[i+1];

                        outL = inL; outR = inR;


                        switch (filt_type) {
                        case 0: // Lowpass GuruFilt
                                    //Left
                                    guru_buf0L = guru_buf0L + guru_f * (outL - guru_buf0L + guru_fb * (guru_buf0L - guru_buf1L));
                                    guru_buf1L = guru_buf1L + guru_f * (guru_buf0L - guru_buf1L);
                                    outL = guru_buf1L;

                                    //Right
                                    guru_buf0R = guru_buf0R + guru_f * (outR - guru_buf0R + guru_fb * (guru_buf0R - guru_buf1R));
                                    guru_buf1R = guru_buf1R + guru_f * (guru_buf0R - guru_buf1R);
                                    outR = guru_buf1R;
                                    break;
                        case 1: // Highpass GuruFilt
                                    //Left
                                    guru_buf0L = guru_buf0L + guru_f * (outL - guru_buf0L + guru_fb * (guru_buf0L - guru_buf1L));
                                    guru_buf1L = guru_buf1L + guru_f * (guru_buf0L - guru_buf1L);
                                    outL = outL - guru_buf1L;

                                    //Right
                                    guru_buf0R = guru_buf0R + guru_f * (outR - guru_buf0R + guru_fb * (guru_buf0R - guru_buf1R));
                                    guru_buf1R = guru_buf1R + guru_f * (guru_buf0R - guru_buf1R);
                                    outR = outR - guru_buf1R;
                                    break;
                        default: break;
                        }

                        psamples[i] = outL;
                        i++;
                        psamples[i] = outR;
            };
            return true;
 }

 void mi::Command(int const i)
 {
            switch (i)
            {
            case 0:
                        MessageBox(NULL,"GuruFilter 1.0\n\nThis is an example created from a buzzdev effects tutorial written by CyanPhase","About XFilter",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:
                        sprintf(txt,"%.1f", (float)value );
                        return txt;
                        break;
            case 1:
                        sprintf(txt,"%.1f%%", ((float)value / 254.0f * 100.0f) );
                        return txt;
                        break;
            case 2:
                        switch (value) {
                        case 0: return ("Guru LP"); break;
                        case 1: return ("Guru HP"); break;
                        default: return NULL; break;
                        }
                        break;
            case 3:
                        sprintf(txt,"%.2f ticks", ((float)value/500.0f) );
                        return txt;
                        break;
  
            default:
                        return NULL;
            }
  }
  
  #pragma optimize ("", on) 
  
  DLL_EXPORTS