CAL course   Selecteer Nederlandse cursus

Introduction
Syntax of CAL
Data types
Events
Constants
Declarations
Interaction with the user
Mathematics
Relational functions
Logical operators
Music time functions
Flow control functions
Creating a CAL program
Running a CAL program

Introduction

During the last years MIDI sequencers are expanded with all kind of digital audio functions. First, the objective was to control external synthesizers. Later, software synthesizer functions were added. The programs from different suppliers became more and more look-a-likes, however, they got their own specific user groups. Cakewalk Professional differentiated itself from version 2, because it offers the user the possibility to add their own functions. With the Cakewalk Application Language (CAL) you can create small programs , which extend Cakewalk/SONAR with your own specific wishes. Subsequent versions added more functionality to CAL, however, the last years CAL did not get the attention, it deserved.

Still, it requires a lot of time and dedication to create a good MIDI sequence. CAL offers interesting possibilities to increase your productivity. Many music lovers are, however, not programmers, and CAL is not a simple language, that you just can apply. On internet you can find a lot of CAL programs, which can be used within Cakewalk/SONAR. As soon as I discover, that I had to do something very often, I mostly create a small CAL program to reach my goal faster.

Here, I like to explain the program language, and its application. I will introduce CAL, as it can be used within SONAR.

CAL is good for modifications of a MIDI sequence. Think about creating a shake or changing the volume (velocity or Expression) of notes to simulate a natural playing style. Also the modification of synthesizer controls is a good example. If a specific basic function is just not what you need, you can create one with CAL.

Cakewalk programs are in general not very big, but when you get smarter with CAL, you will create more complex programs.

The syntax of CAL

First, I want to show the structure of a CAL program. The syntax of CAL is quite similar with LISP, and that if - you do not have programming experience - requires some time to adapt.

Let me show you, how we will subtract two numbers. We normally write this as: 2-1

Within CAL it looks like: (- 2 1)

The minus sign is a function, and the two numbers are the variables which are used in the subtraction. In the following example I will identify the function (operator) with op and the variables with var. A CAL program in its most simple form looks like:

(do (op var var) (op var var))

This nested structure is distinctive for CAL. To make it more readable, we will not write it on one line:

(do ; every program starts with a do statement
(op var var) ; comments are written behind a semi-colon ;
(op var var)
) ; end of do ; a good habit is to identify the end of the do statement
; with ; end of do

Within the do statement we find the program. The do statement contains a list of statements, which are seen as one combined statement.

Before using variables, you have to define them. If they are not defined, CAL will not recognize them.


I will now discuss, how Events are handled by CAL within a MIDI sequence. Well know Events are: Chords, ‘Channel aftertouch’, Controllers, Expressions, Hair pins, ‘Key aftertouch’, Lyrics, ‘Non-registered Parameter Numbers’, Notes, Patches, ‘Registered Parameter Numbers’, ‘System Exclusive Data Messages’, Text, Wave, Windows ‘Media Control Interface commands’ and Wheel. It is a mixture of MIDI, audio and music sheet items.

A MIDI-sequence is organized per track with Events, which happen on a certain time. Please realize that if a MIDI Channel id defined as track properties, all Events in this track will be transferred via this channel of this track. A CAL-program handles selected tracks of part of selected tracks, passes track by track, and reads and/or modifies the Events in the selected track.

To give you a better understanding, I will use a small program as example. In this example all notes which are selected will be set to velocity 100. The selected range will be shown in the CAL user window with the values in the fields:From and Thru.

1 (forEachEvent ; process all Events within the selected range
2 (if (== Event.Kind NOTE) ; if it is a note,
3 (= Note.Vel 100) ; then set the velocity to 100
4 ) ; end of if
5 ) ; end of forEachEvent

I added line numbers, which are normally not present, but they are useful for explanations. The indentation helps to make the program more readable, but are not really needed.

No variables are defined, because they are not needed in this program.

  1. The function forEachEvent indicates that all statements within this function process all Events selected by the user. This can mean all tracks, a part of the tracks or a number of Events in one track. The forEachEvent function is very important, and will be used frequently.
  2. If the Event type (Event.Kind) is equal with (= =)[1] NOTE,
  3. the note velocity (Note.Vel) will be made equal (=) to 100.
  4. Indicates the end of function if.
  5. Indicates the end of function forEachEvent.

In this program you will see, that a statement can contain more than one line:

Now, I will discuss the most important data types, Events, variables and constants.

Data types

CAL knows as variables: integers and strings. An integer can have the size of 16- of 32-bits, and can be unsigned or signed. Integers with a sign are:

int 16-bits, from – 32676 through +32767;
long 32-bits, from less than -2 billion through more than 2 billion.

Integers without sign are:

word 16-bits, from 0 through 65536;
dword 32-bits, from 0 through more than 4 billion

Also, there is the string: a text.

The programmer can define variables with the types as mentioned above, and assign a name to them. We will see that later.

Events

In a MIDI sequence there are Events. An Event is has a type (Event.Kind), happens on a certain time (Event.Time) and is related to a Channel (Event.Chan).

The following type of Events are most important:

Event.Kind Variables Type Meaning Allowed values
CHANAFT ChanAft.Val int Pressure of the channel 0..127
CONTROL Control.Num Int Control number 0..127
Control.Val Int Control value 0..127
KEYAFT KeyAft.Key Int Value of the note 0..127
KeyAft.Val Int Pressure of the note 0..127
PATCH Patch.Bank Int Instrument bank -1..16383[2]
Patch.Num Int Instrument number 0..127
Note.Dur word Length of the note 0..65535
Note.Key Int Value of the note 0..127
Note.Vel Int Velocity of the note 0..127
WHEEL Wheel.Val Int Wheel value >-8192..8191

An Event.Chan can have the value from 0 through 15. In the Cakewalk user window the values will be shown as from 1 through 16.

Event.Time is a variable of the type dword. It will be expressed in raw time (clock ticks). The time can be converted to measure, beat, and ticks (Measure:Beat:Tick).

There are more Events, but they are not important for understanding the concept.

Also, there are system variables, which are displayed in the SONAR‘user window:

From  Marker which indicates the start of the selected range;
Now The position of the bar cursor;
Thru Marker which indicates the end of the selected range;
End The end of the sequence.

These variables are all of the type dword.

Constants

Cakewalk has some predefined constants:

TRUE A Boolean constant indicating the true status. Its value is 1;
FALSE A Boolean constant indicating the false status. Its value is 0;
TIMEBASE This constant provides the number of ticks per quarter note;
VERSION Version number of CAL.

After the introduction of the most important data types, it is time to look at the functions of CAL. The functions can be split into: declarations, assignments, data entry, display, buffer, mathematical, relational, Boolean, ‘control flow, ‘musical time, menu, etc. Next program shows, how CAL can improve your productivity. The program modifies each selected note into a staccato note, and increases the velocity with 25%.

1 (do
2 (forEachEvent ; process each selected event
3 (if (== Event.Kind NOTE) ; if the event is a note
4 (do ; then
5 (= Note.Dur (/ Note.Dur 2)) ; divide the note duration by two
6 (= Note.Vel (/ (* Note.Vel 125) 100)) ; increase the velocity with 25%
7 (if (> Note.Vel 127) (= Note.Vel 127)) ; maximize the value to 127
8 ) ; end of do
9 ) ; end of if
10 ) ; end of forEachEvent
11 ) ; end of do

What is happening in this program? I will explain it line by line:

  1. The program opens with a do statement;
  2. Within the forEachEvent all selected Events are processed;
  3. If the Event is a note, all statements within the do statement are processed;
  4. Within this do statement are all statements nested for the case that the Event is a note;
  5. Divide the duration of the note by two (/ Note.Dur 2), and store the result in Note.Dur (= Note.Dur ‛&‛&..);
  6. Multiply the velocity of the note with 125, divide it by 100, and store the result in Note.Vel;
  7. If the velocity is more than 127, make it equal to 127, because 127 is the highest allowed value;
  8. Close the do statement;
  9. Close the if function;
  10. Close the forEachEvent function;
  11. Close the first do statement, and the program.

Within a nested structure, like (= Note.Dur (/ Note.Dur 2)), CAL evaluates from inside to outside. So, first (/Note.Dur 2) will be calculated, and next the result will be used in (= Note.Dur …..).

Declarations

Before you can use a variable within CAL, you first need to define the variable. Declaration of variables is done as follows:

(int <name> [<value>]) ; [3]declares an integer and sets eventually
;       the value
(dword <name> [<value>]) ; declares a double word and sets eventually
;       the value
(long <name> [<value>]) ; declares a long and sets eventually the value
(string <name> ["This is text"]) ; declares a string and sets eventually the text
(word <name> [<value>])           ; declarers a word and sets eventually the value
(undef <name>)                          ; clears the name from a variable from memory

If we do not set the value, then the value is unpredictable.

Later on I will show you how declarations are used within a program.

Interaction with the user

CAL allows the user to input data, and can display results to the user. Data input functions are:

(getInt <name> <prompt> <minimum> <maximum>)

(getWord <name> <prompt> <minimum> <maximum>)

(getTime <name> <prompt>)

Here is:

<name> the name of the variable in which the data must be stored;
<prompt>          the message that will be displayed in a window on the screen;
<minimum> the minimum value for the data;
<maximum> the maximum value of the data.

You see that data input for an integer variable can be requested with getInt. For getWord applies the same for a word variable. The function getTime is a little different, here the data input must be done in the format Measure:Beat:Tick. This will be converted into raw time’and stored in a dword variable. No limits can be defined with the getTime function.

I will expand the example, given in 'The syntax of CAL', with a request to the user to enter the Note Velocity value.

1

(do

; opens the program

2

    (int nVelocity 100)

; declares the variable nVelocity

3

    (getInt nVelocity “Velocity: “ 0 127)

; request for data entry

4

    (forEachEvent

; process each selected
; Event within the range

5

        (if  (== Event.Kind NOTE)

; If it is a note,

6

          (= Note.Vel nVelocity)               

; then set the note velocity
; at the entered value

7

        )

; end of  if

8

    )

; end of forEachEvent

9

)

; end of do

The added program lines perform the following:

  1. The program is opened with a do statement. All program statements are combined within this do and the closing parenthesis can be found on line 9;
  2. The integer nVelocity with an initial value of 100 is declared;
  3. In a ‘window’ the message 'Velocity' displays the initial value 100. This message is the string between double quotes. Allowed values: from 0 through 127;
  4. until 9. Each selected note is set to the entered value;

The data output functions are:

(message <value> [[<value>] …...])

(pause <value> [[<value>] …...])

(format <value> [[<value>] …...])

These function display a message in the status bar’of SONAR. The pause function halts SONAR. Via a dialog window the user can decide to continue (OK) or to terminate (CANCEL) the process. The output of message and pause in the status bar’can be build up out of a combination of all kind of variables: string, integer, long, word and dword.

The format function is not a real output function, but converts a number of variables into one string.

The function message is mainly used to provide the user extra information or about progress of the program. The pause function is helpful with testing of programs.

I expand the example program again. Now the program will display extra information at the request for input.

1

(do

; opens the program

2

    (int nVelocity 100)

; declares the variable nVelocity

2a

    (message "Allowed values for velocity are: 0 through 127")

3

    (getInt nVelocity “Velocity: “ 0 127)

; request for data entry

4

    (forEachEvent

; process each selected
; Event within the range

5

      (if  (== Event.Kind NOTE)

; If it is a note,

6

         (= Note.Vel nVelocity)               

; then set the note velocity
; at the entered value

7

       )

; end of  if

8

   )

; end of forEachEvent

9

)

; end of do

I will only explain the added program line:

2a.   Before requesting the user for data input, the program displays information in the status bar via the function message. In this message allowed input range is indicated.

Mathematics

Often you need to do some calculations within a program to modify the MIDI sequence. CAL has the following mathematical functions:


(+   <operand1> <operand2>) ; adds operand2 to operand1
(->    <operand1> <operand2>) ; subtracts operand2 from operand1
(*    <operand1> <operand2>) ; multiplies operand2 with operand1
(/    <operand1> <operand2>) ; divides operand1 by operand2
(%  <operand1> <operand2>) ; remainder of the division of operand1
;   by operand2
(++ < variable>) ; increases the value of the variable
;   with one
(--   <variable>) ; decreases the value of the variable
;   with one 
(random <minimum> <maximum>)         ; gives a random value between minimum
;  and maximum

Most of these functions are self explaining, but the modulo (%) function needs some attention. Because CAL does not use fractions and does not round off divisions, the modulo function is needed to obtain the fractional part of a division. The remainder can be found with the modulo function. I will explain this with an example. In this example the version number of CAL is calculated and displayed to the user.

Version numbers in CAL look like: 4.5.  CAL stores its version number in the constant VERSION. Because CAL does not handle fractions, the stored version number is the version number multiplied by 10. In our example the constant VERSION has the value: 45.

The message to the user can be programmed as follows:

(pause "The version of CAL is: " (/ VERSION 10) "." (% VERSION 10))

The number before the dot is calculated with (/ VERSION 10) and the number after the dot is calculated with (% VERSION 10). Analyze how the message is build up from a number of strings and these calculations. The message to the user is: The version of CAL is: 4.5.

The “random” function does not really fit. With this function, you can get a pseudo random number, that will be between minimum and maximum.

Relational functions

These functions are used to compare two numbers. The result of such a comparison (TRUE or FALSE) can be used to influence the flow of a program. CAL contains the following relational functions:

(== <operand1> <operand2>)         ; tests whether operand1 is equal to operand2

(!=  <operand1> <operand2>)         ; tests whether operand1 is unequal to operand2

(<   <operand1> <operand2>)         ; tests whether operand1 is smaller than
                                                             ; operand2    

(<= <operand1> <operand2>)         ; tests whether operand1 is smaller than or equal
                                                             ; to operand2

(>   <operand1> <operand2>)         ; tests whether operand1 is bigger than
                                                             ; operand2

(>= <operand1> <operand2>)         ; tests whether operand1 is greater than or equal
                                                             ; to operand2

The result of such functions is always a value 1 (TRUE) or 0 (FALSE). An example of the usage of such a function is shown in the introduction:

(if  (== Event.Kind NOTE)
; here is the function that will be executed if the result is TRUE
) ; end of if

Logical operators

Logical operators are used to combine tests.  To make it clear, I will give an example. CAL knows two logical operators:

(&& <operand1> <operand2>)         ; TRUE if both operands are TRUE

(||     <operand1> <operand2>)         ; TRUE if one of the operands is TRUE

As example I will use a test whether the Event is a NOTE and the Velocity is equal to 60:

(if  (&& (== Event.Kind NOTE) (== Note.Vel 60))
; here is the function that will be executed if the result is TRUE
) ; end of if

Music time functions

CAL knows some functions for conversion of music time into SONAR time.

SONAR contains measure, beat and tick (M:B:T). In music we know the measure and the beat. For MIDI is a more accurate timing needed, therefore SONAR also uses the tick. The measure, beats and ticks can be converted to SONARs raw time. This raw time is the number of clock ticks from the beginning of the sequence. From the raw time the measure, beat, and tick can be calculated.

(meas <rawtime>)          ; calculates the measure from raw time

(beat <rawtime>)             ; calculates the beat from raw time

(tick <rawtime>)              ; calculates the remaining number of ticks from raw time

Remember that a measure will always be equal to or one or greater than 1. This is also true for the beat.. The number of ticks go from zero to the length of a beat, expressed in ticks.

Reverse calculations can also be made:

(makeTime <measure> <beat> <tick>)

To calculate the start of the fifteenth measure in raw time:

(makeTime  15  1  0)

The constant TIMEBASE, which contains the number of ticks per quarter note, can be used to convert music note length into SONAR note duration.

Flow control functions

Programs will dependent on values of variables follow a different route. You can use relational functions together with flow control functions to achieve this. The already mentioned functions: forEachEvent, “do, and“if”are such flow control functions.

Let us first dig into the important forEachEvent”function. Within a CAL program you will in most cases process selected Events. The forEachEvent” function focus the program on selected Events. This means that Events, which are not selected will not be seen. The function looks like:

(forEachEvent <function>)

In general you do not want to execute one function within the forEachEvent”function. To combine more functions you use the do function. I will first explain more of the if function, before giving an example.

(if  <condition>  < TRUE expression>  [<FALSE expression>])

The condition will be a relational function eventually combined with a logical function.

For better understanding you can also write down the function with explanatory comments:

(if  <condition>           ; if the condition is TRUE
; then
<TRUE expression> ; the function to be executed for the
;   TRUE condition
; else
[<FALSE expression>] ; the function to be executed for the
;    FALSE condition
) ; end of if

In de previous examples I had omitted the optional part else. You will see that here too, only one function can be executed for the TRUE and theFALSE conditions.  Again the do function is needed to execute more than one function.

Next example shows how this works. The program counts the number of selected notes. Notes at the same time will be counted as one note. The variable nCount contains the number of counted note, and the variable nPrevious the time of the previous counted note.

(do
  (word nCount 0)                                         ; define nCount with the initial value 0
  (dword nPrevious 0)                                  ; define nPrevious (time) with the initial value 0
  (forEachEvent                                             ; process all selected events
     (if (== Event.Kind NOTE)                        ; test whether it is a NOTE
         (do                                                         ; the do combines a number of functions
            ; then
            ; test the found note NOTE
            ; if the nPrevious==0 and nCount==0, it will always be the first NOTE
            (if (&&  (== nCount 0) ( == Event.Time 0))
               ; then
               (= nCount 1)
               ; else
               ; if the time of the previous note is unequal to the time of the current NOTE
               (if (!= Event.Time nPrevious)  ; first event on time zero
                                                                       ; set nCount to 1
                    ; then increase nCount and make nPrevious equal to the current time
                    (do                                             ; here we use a do to combine
                                                                       ; a number of functions
                        (++ nCount)                           ; increase nCount
                        (= nPrevious Event.Time)    ; set nPrevious at the current time
                     ) ; end of do
                ) ; end of if
             ) ; end of if
         ) ; end of do
    ) ; end of if
  ) ; end of forEachEvent
) ; end van do and program

Analyze this example; it shows a couple of important CAL constructions.

Besides the do, if and forEachEvent functions, there are also other flow control functions: switch, while and include.

(switch <index> <case1> <case1result> <case2> <case2result> ….)

This function will use the value of index, and compare them with the values in a switch list. I will show a practical example. The following program part shows how to convert notes from one drum set to another drum set.

(switch nNote
    40         (= nNewNote 50)              ; convert Electric snare into Snare low
    41         (= nNewNote 55)              ; convert Low floor tom into Tom bass
) ; end of switch

 This is a way to convert a sequence of notes into other values.

(while  <condition>  <action>)

As long the condition is TRUE, the program within the while loop will be executed. Example: in a sequence at position rPosition I want to know the number of ticks of a beat.

; first we determine the begin of the measure
(= rTemp (makeTime (meas rPosition) 1 0))       ; rTemp contains the begin of the
                                                                                  ;  measure
; Determine the number of ticks of a beat
(while (< (beat rTemp) 2)                                        ; as long as beat is smaller than 2
    (++ rTemp)                                                           ;  increase rTemp
) ; end of while
; now calculate the number of ticks of the beat
(-=  (rTemp  (makeTime  (meas  rPosition) 1 0)) ; subtract the end of the first beat
                                                                                   ;  from the begin of the measure
; rTemp contains now the number of ticks of the beat at rPosition

rTemp is increased till the next beat has been entered. Then we drop out of the while loop. Next the ‘end of the first beat’ is subtracted from the ‘begin of the measure’, and the result is stored into rTemp.

(include <file name>)

This function provides the possibility to include another CAL program within a CAL program.

A good example of this function is the test on the version number of CAL. The example inserts MIDI controllers within a sequence, but first a test will done whether CAL has the correct version level.

(do
    (int nCALVersion 20)                                                  ; required minimal level of CAL
    (include "+need version.cal")                                      ; test the version level of CAL
    (int nControlNum 7)                                                      ; Controller number default
    (getInt nControlNum "Controller number: " 1 127)     ; get controller number
    (include "+Controller.cal")                                            ; process insertion
) ; end of do
; end of program


The included program:

(if (<  VERSION nCALVersion)
    (do
        (pause "MK003: This program requires CAL version " (/ nCALVersion 10) "."
            (% nCALVersion 10) “ or higher”))
         (exit)
     ); end of do
); end of if
; end of program

The program ‘+controller.cal’ is a complex program, that is beyond the scope of this course.  Those who want to investigate this program, can look in the CAL library on these site.

Creating a CAL program

In previous version of Cakewalk a special CAL editor was included. With the introduction of SONAR it has disappeared. Therefore, we need to use standard editors. [4]

CAL program are ASCII text files, which can be created with e.g.‘Microsoft Word’. The above shown examples can be typed in without the line numbers in‘Word. Next you can save them with ‘Save as..’, indicating that it is a text file. You give it the extension ‘CAL’ [5] . The file name must be set between double quotes [6] . The program can e.g. called Note velocity.cal.

Running a CAL program

You select the track or range of the MIDI sequence, that must be processed. Next you call a list of CAL programs by using the pull-down’menu ‘'Process/Run CAL...  Ctrl+F1'’[7] or direct by the key combination ‘Ctrl+F1. From the list you select the program that must be executed and tick Open.

Foot notes:

[1] Between the equal sign is no space.
[2] ‘-1’ means not defined.
[3]
<name>  indicates a required variable name. Variables between [ ] are optional.
[4] A CAL editor in an embryonic state can be found on: http://www.collusioninc.org.uk
[5] The name of the folder in which you need to store CAL files, can be found under SONARs ‘Options/Global…/Folders.
[6] Double quotes are needed to prevent that Word will add the extension TXT at the end of the file name.
[7]
This is the procedure for SONAR. In earlier versions of Cakewalk other menus are used.

Top of this page