DSP blocks are comparatively easy to build. They are usually single functions, and they only require working with NMPML Data instances, rather than whole documents or GUI APIs. The Data class has one of the most complex APIs of any nmpml class, but this i still simple relative to the whole Mien Object model. In addition, you won't need to understand very much of the Data API in order to write useful extensions.
An important point to note is that DSP blocks are inherently special-purpose. The Data class is complex mostly because it is highly polymorphic. Many different types of data can be stored in a Data instance, and the class supports complex nest arrangements of data and metadata of different types. DSP blocks are intended to work in particular tool chains.
Not every block needs to work in every circumstance. It is quite normal for blocks to require that the data they are provided is of a particular type (for example is a time series with at least 2 and not more than 20 channels), or has some set of properties (for example, is mean zero). It's important to document your block, and explain what the requirements are for using it, but it's not required to write extensive type-detection, sanity checking, or exception handling into you block. If a block fails, Mien will display the exception and fail to do the function, but won't abort running GUIs. If possible, your block should execute any statements that are likely to fail before modifying the Data instance. If you stick to that convention, a user who misuses one of your blocks should suffer no ill effects, and can go read your documentation, make needed adjustments to the data, and try again.
The basic structure of a DSP block is a function that expects to receive one or more arguments. The first argument is a Data instance that will be processed by the function. The remaining arguments can contain most simple python datatypes and containers. In principal, you can choose any argument names, data types, and default values (or not) that you want. Your blocks will be much easier to use from inside Mien's GUIs, however, if you adhere to some conventions regarding the arguments.
When a Mien GUI uses you block, it will automatically generate a GUI for the user to enter the block arguments in. By default this gui provides text entry windows for each argument, and attempts to eval the resulting text to arrive at a Python data type. This is easy to use for float, in, and string arguments, but seriously difficult if your argument is, for example, a dictionary mapping tuples of ints onto lists of functions!
Some argument types are common, and are either complex (like the "select" tuple described below) or easier to specify with a GUI than with text (like the position of a visual feature in time-series data). Mien deals with these arguments by providing a set of argument names that get special treatment when the GUIs are generated. If you can use argument names that match these patterns, you can get customized GUIs for your blocks without writing any GUI code yourself.
If you have arguments that take a finite set of possible values, you can also specify the list of choices using special syntax in the function documentation string.
If all else fails, provide a reasonable template for your argument as a default value for that argument when you write your function. This way, users will at least see what sort of code they need to write in your argument GUIs.
If you really need a very complex custom GUI for a particular block, and you can't work around it using these shortcuts, you may also need to write a DV extension to provide a custom GUI for accessing your block.
Automatic construction of GUIs for DSP functions is mediated by the mien.dsp.widgets module (particularly the function getArgChoice defined in that module).
All DSP block functions should have a documentation string (this means that the first line of the function after the "def" statement should be a triple quoted string that describes how to use the block). Adding this string will allow GUI users to browse the documentation for your block from within the Mien GUIs. In addition the doc string is used to implement arguments with a list of possible values (aka switches). Since the doc string is triple quoted, you can include new lines and arbitrary white space inside it. If you start a line in your doc string with the string "SWITCHVALUES" (optionally preceded by white space) you can set up this behavior. Here is an example:
The SWITCHVALUES statement needs a particular syntax, indicated above (even though it's embedded in a comment, it's interpreted by a python-like machine parser). The syntax is as follows:
where argName is the name of one of your block arguments (other than the first one), and PythonList is some legal python list constant containing the allowed values for your argument.
When mien calls DSP functions from Dataviewer, the default values of some arguments are over-ridden by values determined by the current state of the Dataviewer GUI. This is done for any argument name that starts with one of the following strings:
select - This argument name is used for selecting a subset of a data set. It defaults to all channels of the top-level data instance in the range currently displayed by the viewer. See more about "select" below.
xco - (e.g. "xcoordinate1") These arguments get assigned to the values of the current vertical markers that are set in the viewer. You may specify a several of these (xcoord1, xcoord2, ...) and they receive default values in sequential order. If the viewer runs out of vertical markers, it will also use the left and right margins of the current view to assign values to these arguments.
yco or thresh - (e.g. "ycoord1", "ycoordinate", "threshold") These arguments are set in a manner similar to "xco", but using horizontal markers. The bottom and top edges of the current view are not used.
Specialized GUI Browsers
The argument GUIs for certain arguments will provide a "browse" button that launches a GUI tool for selecting argument values of a particular type. As with automatic defaults, these are determined by matching the beginning of the argument name against a pattern. The following patterns are supported:
fname or filename - A file browser is provided
chans - A browser that allows the user to select any set of channels in multichannel data
chan - A browser that allows the user to select exactly 1 channel in multichannel data. The mach against "chans" is checked first, so "chanA" will match "chan" but "chansA" will match "chans".
xcoord - A browser that selects the location of one of the vertical markers in the viewer.
ycoord or thresh - A browser that selects one of the horizontal markers.
upath - A browser that selects any element from the entire nmpml document object that contains the Data instance.
sel - A browser that selects a region of data from the Data instance
dpath - A browser that selects a sub-element of the Data instance (See more about subdata)
events - A browser that selects a sub-data element that provides event type data (See more about data sample types)
newpath - A browser that selects a location for storing a new sub-data element.
attrib - A browser that selects one of the attributes of the current Data element.
Now that you know how to set up your functions, all that's left is to make them do useful behaviors. IIRC Brian Kernighan said that "once you can write 'hello world' the rest of programming is just details." Various folks have also mentioned, however, that "the Devil is in the details."
DSP blocks will make very heavy use of numpy functionality. For general documentation on python and numpy, see this section of the howto.
DSP blocks can ignore a large part of the MIEN API, since they don't usually have to interact with any GUIs, and the only NMPML element they usually interact with is the Data Element (aka an instance of MIEN class "Data"). That's the good news. The bad news is that the Data class has one of the most complex APIs of any single MIEN class, so much so that it has its own page.
For a quick start, you can use only a few functions and methods from the data API to get numpy arrays from your input data set, modify them (usnig numpy functions), and write them back into the data set. See the most common functions section of the data api page.
Remember that your DSP "functions" don't actually return any values. Their entire effect is to modify the Data element in place. This may include changing a data narray or adding subdata elements. Sometimes, your function will generate a simple Python data type (e.g. a float scalar, string, integer, etc.). In this case you may want to store the result as an attribute of the Data element. You can do this using code like "ds.setAttrib('MyResult', returnValue)".