Subsections


Subroutines

MMA supports primitive subroutines as part of its language. The format and usage is deliberately simple and limited ...we're really not trying to make MMA into a functional programming language.22.1


DefCall

Before you can use a subroutine you need to create it. Pretty simple to do. First, here is a subroutine which does not have any parameters:

defCall MyCopyright
   print Adding copyright to song
   MidiCopyright (C) Bob van der Poel 2014
endDefCall

Note that the subroutine definition starts with DEFCALL and is terminated by ENDDEFCALL or DEFCALLEND. The name of the subroutine and any parameters must be on the same line as DEFCALL and ENDDEFCALL must be on a line by itself. The body of the subroutine can contain any valid MMA command or chord data (including other DEFCALL and CALL commands).

Subroutines must be defined before they can be used. This can be done in the main song file, or in a different file you have included (including library files).

So, now you can insert a copyright message into your MIDI file just by calling the subroutine:

Call MyCopyright

Of course, you'll be using the same message every time ... so, let's make it a bit more useful be including a parameter:

defCall Copyright Name
   print Adding copyright to song: $Name
   MidiCopyright $Name
endDefCall

Note that we have a parameter to the subroutine with the name “Name”. In the body of the subroutine we reference this using the name $Name. In this case, to assign copyright to “Treble Music” we'd use:

Copyright (c) 2020 Treble Music

If you need to pass more than one parameter, separate each one using a single comma. Let's assume that you find that you have a large number of 2 bar chord repetitions in your song and you are tired of typing:

Am / Gm
Edim / Gm
Am / Gm
Edim / Gm
...

You could define a subroutine for this:

DefCall 2Bars C1 , C2 , Count
   Repeat
     $C1
     $C2
   RepeatEnd $Count

And call it with:

Call 2bars Am / Gm , Edim / Gm , 7

to generate a total of 14 bars of music.22.2 If you doubt that this is working, call MMA with the -r option (see here).

The parameters in a subroutine can have default values. You can set a parameter default in two ways:

  1. By adding the default value in the header using the parameter=value format. For example, to set the copyright example above, you might use:

    DefCall Copyright Name=Bob van der Poel
         MidiCopyright $Name
    EndDefCall

    in which case you can now use CALL COPYRIGHT to set the value to the default “Bob van der Poel” or you can pass your own value. So,

    Call Copyright

    will set the Midi Copyright to “Bob van der Poel” but

    Call Copyright Susan Jones

    will set it to “Susan Jones”.

  2. You can also set default values by placing a series of DEFAULT messages anywhere in the DEFCALL. For example, the above example could be done with:

    DefCall Copyright Name
         Default Name Bob van der Poel
         MidiCopyright $Name
    EndDefCall

    This produces the same result. Note: any default settings made in the body of the definition will override the parameter settings. It's probably best to adopt one method and stick with that in your code.

    You can also assign NULL or empty values to a variable by using the DEFAULT command (you can't do this in the definition since MMA is looking for a value after the “=”. An empty definition permits constructs like:

    DefCall Copyright Name
      Default Name
      If IsEmpty Name
        print This MIDI has no copyright
      Else
        print Adding copyright $Name to MIDI
        MidiCopyright $Name
      EndIf
    EndDefCall

The concept of default values for parameters is discussed in detail below in the Defaults section (here).

Some points to remember:

Call

As discussed above, you execute a defined SUBROUTINE via the CALL command. There are three parts to this command:

  1. The keyword CALL,
  2. The subroutine name,
  3. A list of parameters to be passed. If there is more than one parameter you must use commas to separate them.

If you wish to have a literal comma in a parameter you must escape it by prefacing it with a single backslash. So,

Call Prt My, what a nice song

will pass two parameters (“My” and “what a nice song”) to the subroutine “Prt”.

On the other hand:

Call Prt My\, what a nice song

passes only one parameter (“My, what a nice song”).

If you have used default values in DEFCALL things get a tad more complicated.

Notes:


Defaults

As noted, above, you can have default arguments for the subroutine parameters. If you have set defaults (using the DEFAULT keyword or a Param=value pair) these will be used for “missing” parameters in a subroutine call. However, if any parameters at all are supplied, they must be in the same order as in the definition. So, if you have created a subroutine like:

DefCall MySub P1 , P2 , P3=somevalue , P4=another value

or

DefCall MySub P1 , P2 , P3 , P4
    Default P3 somevalue
    Default P4 another value

and call it with

Call MySub P1Value, P2Value , This is for p3

the the following settings apply:

$P1 – P1Value
$P2 – P2value
$P3 – This is for p3
$P4 – another value

We can assign a value to a variable by using a “variable=value” pair. This assigns a value to a parameter ... nicely, the order of variables is not important as long as you don't try to use non “=” pairs after one. For clarity, some examples follow (in all cases we use the definition):

DefCall fun a, b=1, c=2
    Print $A $B $C
EndDefCall

Now, with different calling orders:

Call fun 0, b=1

Result: 0 1 2. The “0” is from the first argument, “1” from “b=1” and “2” from the default for $C.

Call fun 0, c=2

Result: 0 1 2. The “0' if from the first argument, “1” is the default setting for $B and “2” is from “c=2”.

Call fun 0, b=1, 2

Result: A MMA runtime error since a non-named argument is used after a named.

Arguments without a default setting can be set with the “=” syntax:

Call fun a=0, c=2
Result: 0 1 2. Here we have set $a from the call, “1” is the default for the second parameter and “2” was set with “c=2”.

When using the “parameter=value” syntax the order of named parameters does not matter:

Call fun c=2, a=0, b=1
Result: 0 1 2. Just as expected.

Any arguments without a default value must be specified:

Call fun b=1
Result: A MMA runtime error since there is no value for $a.

Local Values

MMA tries very hard not to change any variables (macros) you have already set when a subroutine is called. To do this any variables set on the subroutine call line are saved. Their original values are restored at the end of the subroutine call.

Variables you create inside a subroutine can manipulated by saving and restoring them using STACKVALUE (see here). You can return values to the caller (your main MMA code or another subroutine) by pushing a value onto the stack and pulling it off later. However, it is up to the the caller (you) to ensue that the order and number of stack pushes and pulls is correct.

This makes complex (as well as recursive) programming possible.



Footnotes

... language.22.1
If you do solve the Towers of Hanoi using MMA subroutines, please let us know.
... music.22.2
In this case we are using the MMA primitive REPEAT/ENDREPEAT, but it could also be accomplished with a counter, LABEL and GOTO ... we'll leave that as an exercise for the reader.