Procedures

You can write your own software routines (or "procedures") using Igor´s built-in programming language. These routines are entered as text in "procedure windows":

procedure window containing examples of Igor's programming language

In addition to the main procedure window named "Procedure" (which is inseparable from the current Igor "experiment"), additional procedure windows may used to organize your work or to re-use routines previously written by the user or others. Igor ships with about 200 such procedure files written by WaveMetrics programmers.

You can use Igor's built-in programming language to call built-in functions and "operations", of which there are a considerable number (about 550). The Help menu´s Command Help item is a useful guide to these, and contextual help menus provide templates and quick access to documentation.

Procedure windows can also contain text that causes Igor to:

User-defined Functions

User-defined functions are quite similar to functions written in C, Pascal, or modern Fortran and are compiled for speed.

Parameters and Return Values

Functions have parameters and return values. Parameters can be passed by value or by reference. They can be numbers, strings, structures, or references to arrays ("waves") of data. The parameters can be required or they can be optional.

Here's a simple function that counts the number of data values that exceed a given number:

xx

Local and Global Variables

Like nearly every other procedural language, Igor´s language supports local (or "stack") variables (and strings and structures) that vanish when the routine containing them ceases execution.

In the example above the parameters w and value and the local variables exceed, i, and n exist only while the CountValuesThatExceed function is running. Afterwards only the returned value exists, either in the calling routine or in a global if assigned there. For example:

Variable/G gCount= CountValuesThatExceed(wave0,100)

creates a global variable gCount whose value is the returned exceed value. gCount will continue to exist after the end of command execution, and can be observed in Igor´s Data Browser.

Parameters passed by Reference

In Igor, arrays of data are always global objects: they are always passed into a routine by reference as a local "wave reference" parameter. In the example above, w is such a wave reference parameter.

Other kinds of parameters are passed by value unless specifically declared to be pass-by-reference using a convention borrowed from C++:

Function CountTwoThings(w, thing1, thing2)
	Wave 1
	Variable &thing1, &thing2
	
	thing1=0; thing2=0
	...
		thing1 += 1
		...
		thing2 += 1
	...
End

When CountTwoThings is invoked:

Variable loc1, loc2	// two local variable in the calling routine
CountTwoThings(aWave, loc1, loc2)

the values of loc1 and loc2 in the calling routine are changed by CountTwoThings, in a manner familiar to most Fortran programmers.

Without the & character in CountTwoThings, loc1 and loc2 would be passed by value (copied into CountTwoThings), and remain unaffected by assignment and incrementing inside that routine.

Flow-control Commands

Igor´s language is a go-to-less "structured" procedural language with many of the standard modern flow-control constructs, and a few others. Here are some examples:

do-while
	do
		thesum += wave[i];
		i += 1;
	while( i < n )
for-endfor
	for( j=0; j < n*2; j += 2 )
		thesum += wave[j];
	endfor
if-elseif-endif
	if ( i < j )
		val = j - i;
	elseif( i > j )
		val = i - j;
	else
		val = 0
	endif
strswitch-case-endswitch ("string switch")
	strswitch(ctrlNameString)
		case "revert":	// literal string or string constant
			val= 2
			break	// without this execution "falls through" to val=1
		case "okay":
			val= 1
			break
		default:
			val= 0
			break
	endswitch
switch-case-endswitch ("numeric switch")
	switch(num-1)
		case 0:			// literal number
		case kMyConstant:	// Constant kMyConstant =<some value>
			val= 1
			break
		default:
			val= 2
			break
	endswitch
try-catch-endtry
	try
		// test denominator
		if( denominator==0 )
			AbortOnRTE		// this is like C++´s "throw"
		endif
		// use denominator
		val= numerator / denominator
		...
	catch
		// denominator must have been 0
		val=NaN
	endtry

Structures

You can define your own structures and pass them between functions. The syntax for using structure members is similar to C. Structures can contain numbers, strings, arrays of characters and substructures. In addition, Igor has some pre-defined structures such as the WMButtonAction in this simple coding example which also demonstrates that Igor´s structures can be converted into and from strings:

// A window proc that creates a user-defined panel
// with two buttons named b0 and b1.
Window DemoStructuresPanel() : Panel
	PauseUpdate; Silent 1		// building window...
	NewPanel /W=(343,91,561,138)
	Button b0,pos={26,12},size={50,20},proc=ButtonProc,title="Click"
	Button b1,pos={130,12},size={50,20},proc=ButtonProc,title="Click"
EndMacro

// Defines a custom structure
Structure mystruct
	Int32 nclicks
	double lastTime
EndStructure

// The function which runs when Button b0 or b1 are clicked.
Function ButtonProc(bStruct) : ButtonControl
	STRUCT WMButtonAction &bStruct	// Igor-defined structure, passed by reference.
	
	if( bStruct.eventCode != 1 )
		return 0		// we only handle mouse down (code=1)
	endif

	STRUCT mystruct s1			// local user-defined structure
	if( strlen(bStruct.userdata) == 0 )	// button hasn't had userdata initialized
		print "first click on " + bStruct.ctrlName
	else
		// button has a structure packed into its userdata.
		StructGet/S s1,bStruct.userdata	// unpack structure from userdata string.
		Variable lastTime= s1.lastTime	// get a value from the structure.
		String ctime= Secs2Date(lastTime, 1 )+" "+Secs2Time(lastTime,1)
		printf "button %s clicked %d time(s), last click = %s\r",bStruct.ctrlName,s1.nclicks,ctime
	endif
	s1.nclicks += 1			// update the structure members
	s1.lastTime= datetime		// seconds since Jan 1, 1904.
	StructPut/S s1,bStruct.userdata	// pack structure s1 into string inside struct bStruct.
	return 0
End

And this is the resulting user interface:

panel with two buttons labeled "Click" and the print output displayed when they are clicked

Vector and Array Arithmetic

Igor´s programming language allows you to write assignment statements that work on an entire data set or on a subset just like you would assign the value to a single variable in a standard programming language:

Make/O/N=1000 data	// a vector ("wave") to hold 1000 values.
data = sin(2*pi*p/1000)	// assigns to each element of data. p iterates from 0 to 999.
Display data
graph showing one cycle of sine wave

Here's an example of changing only a portion of the array:

data[0,499] = data[p]*data[p]	// replace first 500 values with sqrt(value).
graph showing one cycle of sine wave

For more examples, see Data Math.

#include

Igor encourages code reuse through its "include" mechanism, which is similar to C.

For example, by simply entering this statement in any procedure window:

#include  <All IP Procedures>

Igor adds an Image Processing package to your Igor experiment:

screen shot of particle analysis being performed by Igor´s Image Processing package

The package is comprised of multiple pre-written procedure files stored in the WaveMetrics Procedures folder that ships with Igor, each of which is included from the All IP Procedures.ipf procedure file:

You can reuse your own procedures this way, though with a slightly different syntax:

#include "MyProcedureFile"
which loads the procedure file named "MyProcedureFile.ipf" anywhere inside the "User Procedures" folder (which is inside the Igor Pro Folder).

Constants

Ever look at source code and wonder why some "magic number" was used?

Sleep/T 7200	// an unexplained "magic" number
Sleep/T 60*60*2	// perhaps a little clearer

You can write code that is easier to maintain by using well-named constants:

Constant kTicksPerSecond=60
Constant kSecondsPerMinute=60
Constant kMinutesToWait=2

Sleep/T kTicksPerSecond*kSecondsPerMinute*kMinutesToWait

(Another advantage of using constants is when an identical value must be used in multiple places in the code. If you use a constant name everywhere the value is needed and later need to change the value, you can simply change the definition of the constant to change the value wherever it is used. This is better than combing through the code trying to determine which instances of say, 1, need to change to 2.)

Igor also has string constants, and you can make constant definitions "static" which makes them private to the procedure file they´re defined in:

Static StrConstant ksPrefsFileName="MyPrefs.txt"

User-defined Menus

You can add to Igor´s menus by writing menu definitions in procedure windows. For an example, see the "Image" menu definition in the All IP Procedures.ipf file shown above. You can also enable or disable Igor´s standard menus using the SetIgorMenuMode command. These features allow you to customize Igor for your applications, and to disable Igor features that users of your customized application do not want to worry about.

You can also programmatically add contextual menus to graph, table, panel, and layout windows using the PopupContextualMenu operation:

PopupContextualMenu "Delete;Move;Rotate;" yields a three-item menu