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

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

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