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
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:
print Adding copyright to song
MidiCopyright (C) Bob van der Poel 2014
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:
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
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 measure 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
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:
DefCall Copyright Name=Bob van der Poel
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,
will set the Midi Copyright to ``Bob van der Poel'' but
|Call Copyright Susan Jones|
will set it to ``Susan Jones''.
DefCall Copyright Name
Default Name Bob van der Poel
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.
The concept of default values for parameters is discussed in detail below in the Defaults section (here).
Some points to remember:
As discussed above, you execute a defined SUBROUTINE via the CALL command. There are three parts to this command:
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.
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|
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
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|
When using the ``parameter=value'' syntax the order of named parameters does not matter:
|Call fun c=2, a=0, b=1|
Any arguments without a default value must be specified:
|Call fun b=1|
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.