Macros

Macros are an essential feature of the FELIX data processing philosophy. Macros combine the capabilities of a programming language with the standard capabilities of FELIX, allowing you to design customized processing procedures.

A macro is a text file containing a series of FELIX commands, which together perform a specific task. You can edit a macro file using most text editors. In addition to direct commands, a macro can contain symbol substitution, arithmetic expressions, and flow control statements (that is, for loops, if/then/else statements, goto's, and subroutine calls).

FELIX comes with a library of macros that execute many common processing tasks. You can modify these macros to fit your specific needs. For example, the apodization function in the example macros in Appendix C., Macro examples, will probably need to be different for each user.

In addition to using macros for custom data-processing and -analysis tasks, macros are also used to implement the FELIX graphical user interface (GUI). In fact, the advanced user will find it easy to create control panels and menus to design a custom environment.


Macro directories

You must store custom macros in directories where FELIX can find them. The FELIX macros that drive the GUI are found in the macros folders under the installation directory (by default it is C:\Program Files\Accelrys\Felix 2002. This directory is defined by the reserved symbol macpfx. The user macros can be stored in a directory defined by the reserved symbol macpf4. To keep things simple, you should initially keep all user-written macros in the directory defined by the symbol macpf4. The maximum length for a directory pathway is 128 characters, the same as the maximum length for a single symbol value.

When FELIX searches for macro files, it first looks in the current working directory, then in the directory defined by the symbol macpfx. This symbol is given an initial value in the .felixrc.ini configuration file the installation directory (by default it is C:\Program Files\Accelrys\Felix 2002) that defines the initial directory pathway so the GUI runs by default. If a named macro is not found in the directory defined by the macpfx symbol, FELIX then searches in the directory defined by the symbol macpf1. This logic continues up to the directory defined by the symbol macpf9.


Writing macros

You can write or edit a macro while still running FELIX. To create or change a macro, it is easiest to use Notepad or another text editor. Any editor may be used as long as the macro files do not contain formatting characters and do contain standard carriage control.

It is standard for the first line of any macro to contain the macro's name, though this item is optional. FELIX ignores any macro line that starts with c**. The beginning of a typical macro is shown below. Note that the line numbers at left are used for reference only, and should not be included in the macro:

     1    c**users.mac

2 set 0
3 dr
4 ...

Macros may also contain tab characters and blank lines, so that they may be organized so as to enhance readability. In addition, any characters that come after a semicolon (;) are ignored by the macro interpreter. This makes it easy to add comments to macros as shown below:

1    c**users.mac
2
3
4
5 fra o 10 10 10 10 ;Open first graphics frame
6 fra t 1
7
8
9
10 fra o 30 30 300 300 ;Open second graphics frame
11
12
13
14 set 1
15 sb 600 90
16 dr ;draw sinebell window
17
18
19
20 ret ;return to GUI
21 end

Since macros are simply a series of FELIX commands, you must tell the program when to return control from the macro interpreter to the command interpreter. To do that, add an ret statement before the last end statement.

This ensures that the interface returns to the appropriate graphical state upon completion of the macro.


Executing macros

Once you have created a macro, the standard way to execute it is to issue the execute macro command (ex) followed by the name of the macro. For example, the command sequence shown below executes a macro named test1.mac. FELIX searches for the macro in the current working directory and then in the directory defined by the symbol macpfx, followed by the directories defined by the symbols macpf1, macpf2, macpf3, ..., macpf9.

     ex test1.mac

or:

     ex test1

This command may be issued interactively in the command window, or, alternatively, may be part of an existing macro. The following macro illustrates how a logic operation can determine subsequent macro tasks.

     1    c**color.mac

2 if &color eqs red then
3 ex red_draw.mac
4 els
5 ex blue_draw.mac
6 eif
7 ...
8 end

Another way to execute a macro from the command window is to input the name of the file preceded by a period. This is a quick shorthand notation that can replace the above example. For example:

     > .test1

executes the macro named test1.mac.


Passing arguments to macros

The mechanics of this feature are based on local symbols. Everything following the macro name on the command line is made available to the called macro in the form of local symbols.

When the called macro begins executing, one or more new local symbols are defined. This applies when the macro is executed by ex, cal, or exr. These symbols always have the names: _args, _arg1, _arg2, etc. The first of these, _args, is the number of arguments passed to this macro. When no arguments are given, _args is zero. Otherwise, there are additional symbols _arg1, _arg2, etc., each of which is the value of the nth argument passed to this macro.

CautionDo not use an argument number greater than the number of arguments actually passed to the macro.

For example, the command line:

     > ex test 13 4

causes the macro named test.mac to begin executing, with the following local symbols:

         _args = 2 

_arg1 = 13
_arg2 = 4

The command line:

     > exr find 'Look for' h* n*

causes the find.mac macro to begin executing, with the following local symbols:

         _args = 3

_arg1 = Look for
_arg2 = h*
_arg3 = n*


Loops

FELIX macros allow loop structures that are very similar to the loops in the BASIC and FORTRAN programming languages. Loops allow a set of operations to be carried out iteratively. Loops are necessary for processing multidimensional data for which each vector along a dimension is processed in exactly the same way. Rather than typing in the same sequence of commands several thousand times, you can type in the processing sequence only once and use a loop to apply the specified sequence any number of times.

The basic layout of the loop structure is shown below:

     1    for symbol start_value end_value increment

2 ...
3 ...
4 next

The argument symbol is the name of the loop counter whose value is incremented to reflect the current integer status of the for loop.

The argument start_value defines the initial value of the loop counter, and the argument end_value defines the last value for the loop counter. Both these arguments are inclusive with respect to their values, may be positive or negative, and may be a real number or an integer. If either of these values is a real number, the actual value used is the closest integer value.

The argument increment is the loop counter increment. This defaults to 1 if omitted, but may be set to any non-zero integer. All arguments for the loop sequence may be in the form of symbols.

Macro loops are related to symbol substitution, in that each loop maintains a counter symbol that is incremented each time through the loop. A simple example of a macro containing a loop is:

     1    c**test1.mac

2 for count 1 5
3 ty count = &count
4 next
5 ex &return
6 end

The loop structure is defined by the for and next commands. Commands enclosed between the for and next are executed while incrementing the value of the specified counter symbol for each loop (the symbol count is incremented from 1 to 5 in steps of one). In this macro, the only command inside this loop is the type command (ty), which prints text defined by its command line. The output of the above macro loop is:

     count=1

count=2
count=3
count=4
count=5

The output created by this macro illustrates the operation of for loops. Each time through the loop, the for counter (count in this example) is set to a value corresponding to the number of times the loop has repeated. Symbol substitution replaces the &count parameter with the value of the for loop counter, and the ty command explicitly types out the text following the ty command.

For loops may be nested up to 16 levels deep; however, each loop must have a unique counter symbol. An example of a nested for loop is:

     1    c**test2.mac

2 for row 1 3
3 for col 1 4
4 ty row=&row col=&col
5 next
6 next
7 ex &return
8 end

The inner loop (which affects the symbol col) increments through all its values, the outer loop (row) increments one, and then the inner loop increments through all its values again. Thus the ty command is executed 12 times, for all the combinations of the row symbol from 1 to 3 and the col symbol from 1 to 4. Nested loops are useful for processing multidimensional spectra.


Interrupting a macro

You may want to interrupt macro execution prior to completion. Getting stuck in a lengthy loop is not uncommon when writing new macros for novel data processing. To stop a running macro, type <Ctrl>+\ (press the <Ctrl> and backslash keys simultaneously) or <Ctrl>+c.

For more elaborate handling of interrupt signals, you may use the esc command to escape from a loop. To do this, you must include a specific command structure in your macro that uses the escape command (esc), with the format:

     esc symbol_name

Each time the esc command is encountered, the program updates the argument symbol name. If the <Esc> key is not in the keystroke queue (i.e., you don't press <Esc>), the value for symbol name is set to 0. If the <Esc> key appears in the keystroke queue (meaning you did press <Esc> on your keyboard), the value for <symbol name> is set to 1. As shown in the following example, you can then include logical branching in your loop to specify how the macro is halted:

     1    c**escape.mac

2 for loop 1 9999
3 ty &loop
4 esc out
5 if &out ne 0 quit
6 next
7 quit:
8 end


Loop application: Reversing a matrix

The following is a simple macro used for reversing each 1D vector in a matrix. This example illustrates many of the features described above. It uses the reserved symbol d2size to ensure that each vector along the second dimension is acted upon accordingly:

     1 	c**reversed2.mac

2 for row 1 &d2size ;for each row in the matrix
3 loa 0 &row ;load the row
4 rev ;reverse the data points
5 sto 0 &row ;store the row back
6 ty Row= &row $ ;type the row number,no scroll- ing
7 esc out ;check for escape key
8 if &out ne 0 quit ;quit if escape hit
9 next ;loop to next row
10 quit:
11 end


Loop application: Accessing the database

The following is a macro for deleting cross-peak footprints along the first dimension for a defined region. A macro such as this might be used to remove cross-peak footprints that are part of a T1 streak, which may be defined by the user symbols d1low and d1high:

     1    c**t1streak.mac

2 dba list zero 1 xpk:peaks ;zero list 1
3 get 'D1 low point: ' d1low ;get low limit
4 get 'D1 high point: ' d1high ;get high limi
5 dba list range 1 xpk:peaks cen1 &d1low &d1high pknum
6 for loop 1 &pknum ;for each peak number
7 dba list load 1 &loop peak
;load the first peak number
8 drx xpk:peaks.&peak 0 ;undraw the foot- print
9 dba item delete xpk:peaks.&peak
;delete that footprint
10 ty Deleting peak number: &peak ;talk about it
11 esc out ;check for escape
12 if &out ne 0 quit ;quit if escape
13 next
14 quit:
15 ret
16 end


Branching statements


go statements

Macros allow unconditional branching using variations of the
FORTRAN goto or the switch statement in C. The structure of this command is:

     go label

where the argument label is a specific label identified in the macro.

Labels are identified by adding a colon (:) to the end of the label name. For example, the statement:

     go quit

looks for the label shown below:

     quit:

The go command is very useful when working within interactive macros that prompt you for replies or that need to make decisions based on the values of symbols that are set with toggles or switches.

Below is an example of a simple menu that uses a go statement in this way. The only argument of the go command is a label name, which may be explicit or symbolic; for example:

     1    c**gomacro.mac

2 ....
3 ....
4 go quit
5 ....
6 ....
7 quit:
8 ty All done!
9 end

When the go command is executed, the macro jumps to the specified label (in this case, quit:) and continues executing.

Labels may be up to sixteen characters long and represent a specific place within a macro file. Within any macro, each label name must be unique. When a label is branched to, the commands that follow it are executed. In the example shown above, after the go branch to the quit: label, the message All done! is printed to the screen. If the specified label is not found within the macro, the macro continues on to the next line as if no go statement existed.


gto statements

Macros also allow branching using a case-type go to command (gto). This command determines branching based on a fixed integer, which is usually a number defined by a symbol. The format of this gto command is:

     gto symbol_value value0 value1 value2 valueN

If the argument symbol_value equals 0, the macro branches to the label defined by value0. Likewise, if the argument symbol_values equals 2, the macro branches to the label defined by value2.

In the FELIX GUI, the gto branching statement is used to make decisions based on symbol values defined by control panel toggles or switches. For example, a symbol defined by a toggle can be used to choose between Fourier transform options. For this example, if the particular symbol called fttype (defined by a specific toggle) has a value of 0, a FFT is specified. If the symbol fttype has a value of 1, a BFT is needed. We could then create the following macro to make use of this information:

     1    c**toggle.mac

2 mnu p transform 20 4
3 if &button eq 0 quit
4 gto &fttype fft bft
5 fft:
6 ft
7 dr
8 go quit
9 bft:
10 bft
11 dr
12 go quit
13 quit:
14 ret
15 end


gif statements

Macros also allow branching using the arithmetic if command (gif). This command determines branching based on the sign of a fixed integer, which is usually defined by a symbol. The format of the gif command is:

     gif symbol_value value_lt_0 value_eq_0 value_gt_0

In practice, if the argument symbol_value is less than 0, the macro branches to the label defined by value_lt_0. If the argument symbol_value equals zero 0, the macro branches to the label defined by value_eq_0. And if the argument symbol_value is greater than 0, the macro branches to the label defined by value_gt_0. The gif statement is commonly used in GUI macros.


if statements

Macros can contain if statements for conditional branching. if statements are similar to go statements in that they can result in a branch to a specified macro label, but differ in that the branching may or may not happen, based on the specified condition. The first type of if statement consists of a conditional and a target label:

     if conditional label

Another type of if statement, the if/then/else, is used to control the execution of blocks of code depending on the state of the conditional. The form of the if/then/else is:

         if conditional then

...
else
...
eif

The most basic conditional consists of two strings and a relational operator and has a value of either true or false. The conditional is evaluated by comparing string1 with string2 using the specified relational operator. A typical if statement is:

     if &number eq 0 quit

This command compares the value of the symbol number to 0 using the eq (equal to) relational operator. If the symbol number equals 0, the value of the conditional is true, and a branch is made to the label quit:. If the symbol number does not equal 0, the conditional is false, no branch is taken, and the next command in the macro is executed.

Conditionals use two different types of relational operators. Arithmetic relationals interpret the strings as numbers, and string relationals interpret the strings as ASCII characters. Because numbers and ASCII strings do not sort in the same order, you must be aware of the ASCII code when using string relationals other than eqs and nes. A list of the relationals is shown below for both arithmetic and string characters:

Arithmetic relationals:

eq -- equal to

ne -- not equal to

lt -- less than

gt -- greater than

le -- less than or equal to

ge -- greater than or equal to

String relationals:

eqs -- equals

nes -- not equals

lts -- less than

gts -- greater than

les -- less than or equal to

ges -- greater than or equal to

Conditionals can be combined using and and or operators to test more than one relation at a time. The and operator yields a true result only when it operates on two true conditions. The or operator yields a true result if either conditional is true. Examples of conditionals containing the and and or operators are:

     string1 operator string2 and string3 operator string4

string1 operator string2 or string3 operator string4

An example macro that uses if commands is given below. This macro plays a guessing game using a binary search and uses a variety of flow-control commands that are discussed above.

     1    c**test4.mac

2 ty Guess a number between 1 and 10=
3 ready:
4 get "Are you ready? (y/n):" yesno
5 if &yesno nes y ready
;ask again if not ready
6 def lo 1 ;define the necessary symbols
7 def hi 10
8 def old 0
9 again:
10 eva middle ((&hi+&lo-1)/2) 1
;guess middle of range
11 if &hi eq &lo done ;we are done
12 if &middle ne &old guess
13 if &middle eq &hi mlo
14 def middle &hi
15 go guess
16 mlo:
17 def middle &lo
18 guess:
19 get "Is it bigger than &middle?" yesno
20 def old &middle
21 if &yesno nes y no
22 eva lo (&middle+1)
23 go again
24 no:
25 def hi &middle
26 go again
27 done:
28 ty The number is &middle.
29 ret
30 end

This macro uses if commands containing both numeric and string relational operators. The string relational nes is used to distinguish between the user responses, while the numeric relations eq and ne are used to compare numbers. The eva (evaluate) command is used twice to force evaluation of expressions.

Another macro example using if statements follows. In this example, the gv command gets the value of a datapoint from the workspace and places it in a specified symbol, to find the largest, smallest, and average value in the workspace:

     1    c**test6.mac

2 gv 1 bigpt
3 def smlpt &bigpt
4 def tot 0
5 for point 1 &datsiz
6 gv &point thispt
7 if &thispt gt &bigpt newbig
8 if &thispt lt &smlpt newsml
9 go loop
10 newbig:
11 def bigpt &thispt
12 go loop
13 newsml:
14 def smlpt &thispt
15 loop:
16 eva tot (&tot+&thispt)
17 nex
18 eva avg (&tot/&datsiz)
19 ty Biggest=&bigpt
20 ty Smallest=&smlpt
21 ty Average=&avg
22 ret
23 end

This example first sets the symbols bigpt and smlpt to the value of the first datapoint and the symbol tot to zero. The macro then loops through all datapoints (from 1 to &datsiz), comparing values with bigpt and smlpt. If any point is larger than bigpt or smaller than smlpt, the new large or small point is saved. The sum of all point values is accumulated in tot, which is used to calculate the average.


if/then/else statements

Macros can also contain if/then/else structures for more orderly flow control. The if/then/else statements may be nested up to 16 deep. The basic if/then/else/eif structure is:

     if conditional then

...
els
...
eif

The condition is evaluated by comparing string1 and string2 using the specified relational operators described above. A typical if/then/else/eif statement is:

     if &number eq 0 then

ty ----------
type Symbol not defined
els
eva count (&value/&number)
ty ----------
ty Symbol count is: &count
eif

In the example shown above, the if/then/else/eif structure is used to prevent a divide by zero error if the value of the symbol number is 0.


ifx statements

Macros can contain ifx statements for conditional macro execution. ifx statements are similar to if statements in the arguments required; however, they differ in that they result in the execution of another macro instead of branching to a label in the same macro. The basic ifx command consists of a conditional and a macro filename:

     ifx conditional macro_name

The conditional is evaluated in the same way as in the if statement. A typical ifx statement is:

     ifx &number eq 0 root

This command line compares the value of the symbol number to 0 using the eq relational operator. If the symbol number equals 0, the macro named root.mac is executed.


exr statements

The exr command (execute and return) is a powerful way to execute macros. This command brings the concept of a subroutine call, as used in high-level programming languages, into the FELIX command arsenal.

The exr command preserves where you are in the current macro, loads and runs the new macro, and resumes exactly where it left off when the new macro finishes. Macros executed via exr may be nested up to eight levels deep.


Macro-specific commands

Several commands are used only within macros. For example, the esc command described above is useful only in a macro. There is no reason to check for an <Esc> key unless a macro is running, since otherwise there is no process to interrupt. Some other examples are shown below.


com statements

Communicate statements (com) pause an executing macro, prompt the user for a single command line, execute that one command, and then restart the macro after the pause point. For example, the com command can be used to test a range of window functions as they affect a dataset:

     1    c**read1d.mac

2 rn test.dat
3 com
4 bft
5 dr
6 end

In the above example, at the com command (line 3), a FELIX prompt appears and waits for the user to enter any FELIX command they desire.


err statements

Error statements (err) define an error trap destination for a macro. If any command sets the value of the reserved symbol status to non-zero, it means that command didn't function properly. If you use an err statement, the macro resets the status to 0 and continues executing where the error trap indicates. There are currently two types of err commands. If the command:

     err label label

is encountered, the macro branches to the specified label. If the command string:

     err macro macro

is encountered, macro interpretation is transferred to the specified macro. Error traps are most commonly used in macros that read user data.


ty and tym statements

The ty statement types the following text to the output window; the tym statement types the following text to the status bar. This text can be used for detailing the status of any macro.

For example:

     tym All done!!

ty Data size is: &datsiz
Note: Symbols may be substituted in any macro command.


Using FELIX to build macros

Macros may also be created from within FELIX using simple program commands. This technique is useful for saving a sequence of commands for execution later. For example, spectrum annotations are saved to a macro file. When you want to redisplay the annotations, simply execute that annotation macro.

Before building a macro using FELIX commands, check for the presence of a macro file with the name you want to use. Do this in your macro with the inquire (inq) command, which accepts the input string:

     inq prefix file_name symbol

The prefix argument reflects the filetype based on the prefix, whether it is a macro (mac), annotation file (.ann), datafile (.dat), or matrix (.mat) file.

For files without prefixes, set the prefix parameter to nul. Based on the prefix type, FELIX uses the directory defined by the *pfx symbol to locate the file. For example, if prefix is mac, FELIX looks in the directory defined by the symbol macpfx to determine whether a file named filename exists. If the file is found, the symbol defined by the argument symbol is given a value of 1. If the file is not found, the symbol defined by the argument symbol is given a value of 0. Below is an example that uses the inq command to determine whether a file named test exists in the macro directory:

     1    c**inquire.mac

2 def filnam test
3 inq mac &filnam exist
4 if &exist eq 1 fileok
5 ty ----------
6 ty File &filnam not found.
7 go quit
8 fileok:
9 ty ----------
10 ty File &filnam exists.
11 quit:
12 ret
13 end

Before building a macro using FELIX commands, you must first open that file. The open file command (opn) performs this function and accepts three arguments:

     opn prefix file_name overwrite/append

The prefix parameter defines the prefix for the new file and places that file in the directory defined by the corresponding pfx symbol. As discussed above, if prefix is mac, the resulting macro file is created in the directory defined by the symbol macpfx.

The file_name argument is the name of the file to be created. The overwrite/append argument defines whether you want to overwrite an existing file (0) or append to an existing macro file (1). Setting overwrite/append to 1 is useful for modifying macros that already exist, without recreating them from scratch. Two files can be opened simultaneously, providing one file is opened for output only.

Once a macro file has been opened for writing, the put command (put) is used to add text to the file. The put command adds all text following it to the file, including spaces. Quotes are not needed. Sample put statements are:

     putdef annsize &annsiz

putdef thick &thick
putdef level 1.0

Open macro files must be explicitly closed before execution, using the close file command (cls), which requires no arguments.

Since you are creating a macro, it is very important that before closing it, appropriate end and return statements are added to the file. The end command must be the last line in a macro in order for the program to switch from the macro interpreter to the command interpreter. Similarly, the ret statement must be at the end of a macro if you want it to return to the GUI. An example of building a macro is shown below:

     1    c**build.mac

2 def return root
3 opn mac cplevel 0
4 putc**cplevel.mac
5 putdef level &mylevl
6 putdef nlevel &mynlev
7 putdef pencol &mycolr
8 putdef cycle &mycycl
9 putex &return
10 putend
11 cls
12 end

The resulting macro, named cplevel.mac, is created in the directory defined by the symbol macpfx:

     1    c**cplevel.mac

2 def level 1.2
3 def nlevel 10
4 def pencol 2
5 def cycle 3
6 ex root
7 end

This macro can now be executed.

When building macros in this way, you may want to place a symbol-definition statement point to another symbol instead of a value. In the example above, if you changed line 8 in the build.mac file to read putdef cycle !&newcyc, line 5 in the cplevel macro would read def cycle &newcyc. Delayed symbol substitution such as this greatly increases the power of the program.


Some simple macro applications


Reading files

FELIX macros are the most powerful and innovative feature of this NMR processing software. By writing your own macros, you can automate anything that you can do with FELIX manually.

Your ability to transform and manipulate data automatically using macros is limited only by your imagination. For example, if you want to read a datafile (rn), apply an exponential window function (em), Fourier transform (ft), phase correct (ph), and draw a spectrum (dr), you could write the following macro and name it
process.mac:

     1    c**process.mac

2 get 'Enter file name:' filnam
3 rn &filnam
4 em 3
5 ft
6 ph
7 dr
8 cl
9 ret
10 end

The get command prompts you with "Enter file name:" and waits for a datafile name to be input. The read command (rn) reads the file (notice the use of the symbol filnam). Then the data are multiplied by a 3 Hz exponential window (em 3). The data in the workspace is then Fourier transformed (ft), phase corrected (ph), and displayed on the current graphics terminal with the draw command (dr). The ex &return statement returns the macro to the GUI. The end command (end) is mandatory at the end of every macro; this tells FELIX to stop executing commands from the macro and accept input from you again. Instead of manually entering the six commands listed above, you can simply enter:

     > .process

or:

     > ex process

from the command line.


Plotting multiple files

Macros can be used to perform repetitive tasks by constructing loops that process several datafiles in a similar manner. For example, if you want to generate hardcopy plots for a series of related 1D spectra, you might construct a macro similar to the macro mplot.mac:

     1    c**mplot.mac

2 again:
3 get 'Enter file name (quit):' filnam
4 if &filnam eqs quit then
5 ty Returning to &return
6 ex &return
7 else
8 rn &filnam
9 em 3
10 ft
11 ph
12 dr
13 hcp
14 eif
15 go again

As in the previous macro, you are first prompted for a filename, then the data are multiplied by an exponential, Fourier transformed, phased, and drawn. The spectrum on the screen is then plotted on the current hardcopy device with the hardcopy plot command (hcp). After the first spectrum is plotted, the mplot.mac macro branches back to the label again: and prompts for a second datafile, which is then processed and plotted. In this macro, to quit you enter quit instead of an actual filename.