Not everything about CAL is straight forward. There are allot of aspects of CAL that are undocumented and in some cases, mis-documented. There are subtle features to some functions that don't present themselves on the surface. You can do things with CAL that wouldn't occur to you just off-hand. Then again there are the quirks in CAL that make you want to pull your hair out! In an attempt to forestall unnecessary baldness among CAL users, I wish to provide you with some of the fruits of my own hair pulling sessions with CAL. Keep in mind that there must be at least as much that I have yet to discover. Perhaps with the collective efforts of all of us CAL hackers, we can keep each other sane. Here is my opening contribution to this cause. One thing I wish to convey as a sort of disclaimer is that you may never see some of these reports of strange behavior. Some of them I myself have only seen once or twice and perhaps they are a result of some odd conflicts in the registry or whatever. I still feel compelled to mention them JUST IN CASE someone else out there comes up against the same problem. This way, they will have at least been briefed on the possibility and can take action accordingly. Fact is, I hope nobody ever runs across the oddities that are listed here, but you will at least some of them, believe me!
By and large, anything you write with CAL on a system running Cakewalk version 3 should load and run fine on any version after it. The same applies to stepping up from version 4, 5, 6 etc. to any higher version. However (and this should come as no surprise), the reverse is not true. Not only are some functions different or even unavailable in earlier versions, but the system itself can impose limitations. One of the most obvious came to me when I was working with one of the sample CAL programs included with this document called CROSFADE.CAL. I wrote it in version 3 and had to place some limitations on it due to system constraints. After reworking it in version 7, it no longer ran in version 3, not because I had added features not supported by version 3, but because it overran the abilities of the version 3 run-time interpreter. I added two more nesting levels to the program when I added the ability to select "none" for the amount of attack and/or decay time in the crossfade of each note. On version 7, this was a great new feature but in version 3 it generated an "evaluation stack overflow" error due to the addition of the new and deeper nesting of some functions. My indentions had gone from 8 to 10 tabs deep in the code text and version 3 simply can't keep track of that many calls. In that I realized I would have to have separate versions of CROSFADE.CAL for version 3, I also included in the new version the ability to deselect the target track and select a new target track for performing the volume event integration on the cloned area, a feature impossible in version 3 because there is no way to deselect the "blue-highlighted" track. In later Cakewalk versions you can deselect all tracks and select new ones through CAL even though if you were to read the CAL help in these later versions it will try to convince you that the "TrackSelect" function doesn't work any more - WRONG!!! It works just fine. There are some limitations to it now, like the fact that some functions, the (insert) function for one, will not honor the (TrackSelect) function unless it is within a (forEachEvent) loop. If you try to use a (TrackSelect) and it doesn't work, you may have just run into this limitation.
Don't forget support for long file names in native Windows 95 versions of Cakewalk. Calling an "include" function with a long file name will get you a fast error message in versions running under Windows 3.1. Another consideration is a minor kink in the CAL execution system in versions 7 and 8. If you load a CAL program or otherwise attempt to use CAL without a .WRK file loaded into the workspace, there is no way of running the CAL program. Even clicking on the "RUN" icon in CAL View will have no effect at all. The only time this is a problem is if you have cleared the workspace and then want to load some files or format some default workspace to your liking in one step by running a CAL program that pre configures everything just so. You can forget it! You must load SOMETHING into the workspace first, even if it's just a default blank workspace. Now you can run your CAL set-up program. This isn't a problem when you first load Cakewalk as you are given a default blank workspace as soon as the program finishes loading. It's only a problem if you have closed everything and the workspace is completely vacant.
In the first part of this document, I discussed some of the qualities of a sequence. One of the things I touched on was the ability later versions of Cakewalk have of splitting tracks into non-sequential groups of "sub-tracks" in response to complex editing that result in MIDI event clips overlapping. This can be a real problem to CAL, but there is a way to correct these "uncorrelated" tracks.
For the sake of background, the way I found the problem is that I have some CAL programs that are very sensitive to the order of events in a sequence. They rely on calculating the distance between notes or the order of controller events or whatever, but in some way, if the events don't fall in the exact order in the data stream that they occupy in the "music", then the CAL program fails to properly calculate and use these relational factors leading to some rather odd results. I remember scratching my head over a CAL routine that was supposed to calculate the distance between notes and act only on notes that have a set minimum distance between them. I kept getting activity on notes that were too close to their neighbor to qualify and couldn't figure out why. After fussing over the results, I concluded that the only thing that could cause this is if the note in question's neighbor was somehow "invisible" to CAL. I remembered that I had done some rather elaborate editing in this section of the sequence (I had been splitting a two-handed piano track into left and right hand tracks) and I wondered if all of the notes were still where they were supposed to be. I wrote a quick CAL program that did nothing but look at event times and display them on the message bar. I saw the problem. The event time numbers started out low, went high, then went back low again and started counting high all over. This was it! Now, how do I fix it?
For a time, I thought all I could do was convert the file into a version 3 file that didn't support clips and that would blend everything. This worked but was a pain in the butt. My friend and brother CAL freak Alan Myers, who spent a good many years working on the Cakewalk development team, told me to try selecting the offending track and in the CLIP Pane of the TRACK View, do a right-click of the mouse and select "Combine", and all of the clips, overlapping and otherwise, should be blended into one big clip with all events now in proper order. I've tested this idea and it works! If you need to keep the clips separate that don't overlap, just highlight the clips one at a time and combine them with themselves. This way, any overlapping clips will be blended into a single clip but non overlapping clips, which don't cause any problems, will still keep their separate identities. Again, as I mentioned in the intro page of this tutorial, an even better answer is just to make sure to select "Paste Into Existing Clips" in the PASTE dialog whenever you are about to blend events on the same track. This will prevent any overlapping clips from forming.
I would advise that you "include" my TIMERROR.CAL program at the beginning of any order-sensitive programs you write in order to test for and abort in the presents of a non-sequential track. Upon finding such a track, perform the above procedure on it and then you should be able to run your program without any problems.
As mentioned several times elsewhere, the "EditInterpolate" function doesn't work correctly in version 6. In other versions, it responds to the "From" and "Thru" marker values without any problem, operating only on the selected portion of the sequence defined by these markers. In version 6, "EditInterpolate" will not respond to the markers and will operate on an entire track from beginning to end regardless of the values in "From" and "Thru".
As it turns out, it is possible to switch the "EditInterpolate" function's response to the "From" and "Thru" markers back on. I discovered this quite by accident one day and this is how I did it. Just before the function statement, you add a pair of "dummy" statements that don't have any use as CAL operations, but somehow tell the "EditInterpolate" function that follows to use the markers. Just type in these two lines above the (EditInterpolate 1) function as shown:
(= From From) (= Thru Thru)
Only now will CAL honor the values in these markers. This can lead to some real confusion in writing CAL programs using "EditInterpolate" if you aren't aware of this oddity which is, even more oddly, completely undocumented. Also, keep in mind that you must do this just before every single "EditInterpolate" function in your program as one instance of these "dummy" statements isn't enough to condition all of the interpolation functions in a program. For convenience, if you are writing code that needs to be portable across several versions of Cakewalk, just make it a practice of writing "EditInterpolate" functions as mini macros of three lines; the two dummy statements and the interpolate statement. In other versions of CAL, the extra statements will have no effect, neither good nor bad. In version 6, they will save your butt!
From version 3 to version 7, there have been changes along the way in the representation of selected tracks. In every version, one track is always implicitly selected regardless of any other action. This implicitly selected track is the one with the heavy outline around one of its parameter boxes. In version 3, it is also distinguished as the track with the track number highlighted in blue. In version 7, it also shows its track number a shade brighter than the rest of the track numbers. Any track or tracks, including the implicitly selected track, can also be explicitly selected by the deliberate action of the user or through a CAL program. Explicitly selected tracks are shown by their track numbers highlighted in black or deep blue if you are using the default colors. The thing about an implicitly selected track is that with no explicitly selected tracks available, the implicitly selected track will be the default focus of many actions that the user can take including, for example, opening the Event List view. On the other hand, explicitly selected tracks are the only ones subject to the user's editing actions and the operations of CAL programs. At least this is so for versions other than version 3.
If you perform an edit or run a CAL program in version 3, the implicitly selected track will always be effected along with any other tracks that might be explicitly selected. The drag is that although any explicitly selected track can be selected and likewise deselected at will, either by clicking on the track or by executing the (TrackSelect) function from CAL, the implicitly selected track highlighted in blue cannot be deselected, only changed from one track to another by the user. Unfortunately, it is equally subject to any actions about to befall the explicitly selected tracks. It impossible to have NO selected track in version 3. This is not the case for the other versions. In versions 4 and above, the implicitly selected track is not subject to editing or CAL actions unless it is also explicitly selected.
This selection characteristic in version 3 can lead to problems when running a CAL program that operates on several tracks and must therefore be free to select and deselect these tracks as needed. As these tracks are selected, acted upon, deselected and other tracks selected, the blue highlighted track will be equally effected each time. An example is a program that selects a range of tracks and sets all of the events on each track to a unique MIDI channel number. Because this program selects the tracks one at a time and then performs an interpolation on them, the blue highlighted track is also interpolated over and over each time some other track is selected and operated on. Thus the problem. The solution is to operate on all explicitly selected tracks first and then deselect all other tracks and interpolate the events in the blue highlighted track last. This has the effect of "undoing" all of the undesired interpolations the track was subjected to while each of the others were selected and operated on. There may be cases when an action is carried out that can't be "undone" in this manner. In cases such as this, it's important that the user place the blue highlight on an empty track so that as CAL selects tracks for editing, these explicitly selected tracks are the only ones with any data that will be effected.
From time to time, I will be writing code and get an error that I can't clear no matter what I do. For example, I may get a "Missing one or more closing parentheses" error and not be able to find the offending part of the program. There were times when I have had to scrap the entire program and re-enter all of the code again in order to get it to run, even though the old and new code are identical to the eye. As it turns out, they may not be identical to the CAL interpreter. Things that the eye can't see in the editor window are plain as day to the interpreter.
I have discovered that I could have what appears to be bullet-proof code and still not be able to clear a "Missing one or more closing parentheses" error. In fact, I tried once to remove entire chunks of a program to make it go away only to be left with
and still I got the error! I then added the "NIL" so that it read
(do NIL )
just like you see when you first open the CAL VIEW, and still it was there. I ended up saving the fragment to disk along with a copy of the same fragment after performing a NEW, then looked at both with a hex editor to see why one would run (not do anything, but at least it would run) and the other wouldn't. What I discovered surprised the hell out of me! The only difference was that the working code had two line feeds at the end and mine didn't. I went back to my full program, added two line feeds at the end and BEHOLD! It ran!
Sometimes a "Syntax error" will crop up and display a chunk of text from a comment line. I have found that using double semi colons at the start of the offending comment line will clear it. I don't know why, it just does. There have been other times when no matter what I do, I still got some sort of error or another, and the only way I could solve this problem was to remove some or all of the comment lines in the program. There have been times when I have had to wipe the screen clean and enter the code all over again! Again, I don't know why this is true, but it is. Perhaps a stray non-displaying byte somehow finds its way onto the screen. You can't see it, but CAL does and simply can't figure out what to do with it. The only solution is to clear some of the text (and presumably the bogus byte with it), and start over. This hasn't happened often, but it has happened more than once to me in both versions 3 and 6. It has also happened to other people I know.
If you fail to indent the lines after the opening "(do" in a program by at least one tab space, you may get errors. It seems that CAL needs these tab characters in the code to keep the functions strait and thus interpret the code correctly. As a rule, this isn't a problem in that good programming practice will dictate the use of tabs to indent each nesting level as the code is entered and so this problem should never arise. However, if you get in a hurry to test some small fragment and in your haste fail to indent at least one tab space in every line except for the opening "(do" and closing ")", you may not get the code to run even if it is otherwise flawless.
If you check the section where I discuss the "do" function, you will see mention of another strange quirk in the CAL system. I had a section of code in one program made up of cascading "if" statements like so:
(if (something or another....) (if (some more stuff...) (if (yet another statement...) (do (a bunch of stuff...) (and so on...) (and so forth...) ) ) ) )
For some reason, CAL would not see the final "if" or any of the code nested under it. I got no error message or any other indication of a problem, it was simply ignored regardless of the outcome of the "if" statement above it. In order to test for CAL even going to the code, I added a "pause" statement to stop the program and let me know if CAL had gotten that far. In order to use the "pause", I had to add a "do" to the stack of statements like so.
(if (something or another...) (if (some more stuff...) (do (pause "found the IF statement!") (if (yet another statement...) (do (a bunch of stuff...) (and so on...) (and so forth...) ) ) ) ) )
After adding the "do" and "pause", the program worked perfectly. I couldn't figure it out, but I went ahead and removed the extra "do" and "pause" as well as the closing ")" I had added for the "do" and again the program stopped working. I finely went back and put the "do" back in without the "pause" and everything was fine. I know that the "do" is unnecessary according to the syntax of CAL, and yet the program would not run without it. Go figure! If this ever happens to you while cascading a bunch of "if" functions, try adding a "do" to the spot where CAL seems to be getting lost. It might be all you need to make things right.
Believe it or not, I've had situations where the order of variables in a comparison involving zero had an effect on the ability of a function to generate an outcome. This sometimes will not work:
(if (> 0 count)
but if you rewrite it like this:
(if (<= count 0)
it will run just fine. It seems that there are times when placing the zero first and then the other variable will cause trouble.
I must mention one more thing. I started playing (a bit roughly, I must admit) with CAL in version 7 one day and caused it to enter some funky mode where every function caused a Syntax Error to be reported. All I could do was restart Cakewalk and everything was fine. I never could reproduce this problem a second time, but just to be safe, if you ever get into a situation where CAL or any other part of Cakewalk stops obeying the rules, save your work, shut 'er down and restart. That does the trick 99% of the time. For those 1% cases, restart Windows!
In all fairness, I must mention that all of these quirks I have listed can be somewhat "intermittent" in nature. That is to say that you can do something and have a problem in one case and do the exact same thing and CAL wouldn't even bat an eye in another case. I'm at a loss to explain this except to say that there are subtle differences in your Windows environment that may not be noticeable to you but can raise havoc with your applications, Cakewalk included. It could be something as obvious as having too many other applications open at the same time to obtuse circumstances like Windows performing housekeeping on the swap file at the exact instant you hit "run". All I can offer are these observations and solutions as they have worked for me and the advice that you take up meditation for stress relief.
In case you might be curious, there are ways of crashing Cakewalk through the CAL editor. If, for example, you take a "new" CAL screen in version 6 and in place of the "NIL" type in (if ) and hit the RUN key, you will get a Windows system error and Cakewalk will shut down. If you really want to be malicious, try setting the variable "Now" to a very large 32 bit value! Supposedly, the marker variables are double words, which means they should be able to hold any 32 bit number from 0 to 4294967295. However, if you try to set "Now" to any number near this maximum value, not only will Cakewalk generate a system error, but if you keep re-opening Cakewalk and doing it again, you'll eventually blow it right out of the water! When you restart Cakewalk the next time, you'll discover it has forgotten its MIDI input assignments. I gather Windows still thinks there is an open copy of Cakewalk running and refuses to open the same MIDI devices for another instance of the program. In any event, I thought it prudent to restart Windows when it happened to me. By the way, all of these problems were corrected in version 7.0. So far, I haven't found anything in CAL that will make this baby fall.
One other thing worth mentioning. My fellow CAL hacker Dean Brewer in South Africa told me about a CAL program he had written to chase down duplicate events that wouldn't run even though it was, by all appearances, flawless. It kept giving him "Expression too complex" errors. I did some playing around with it and discovered there is a limit to the number of functions a CAL program can handle. That limit is 256. If you have more than 256 functions, you will generate the error. Here is the layout of Deans program, edited for space:
(do (TrackSelect 1 -1) (= From 0) (= Thru 5) (a bunch of edit filter setup functions) (say, about 10 or 12 altogether) (EditDelete40 1 1 1 0 0 0 0) (TrackSelect 1 -1) (= From 0) (= Thru 5) (a bunch of edit filter setup functions) (say, about 10 or 12 altogether) (EditDelete40 1 1 1 0 0 0 0) (another section like those above) (more sections like those above) (more sections like those above) (more sections like those above) (more sections like those above) )
Altogether there were 11 such groups of functions, each group made up of anywhere between 8 and 15 functions. To make a long story short, there were 261 functions between the opening "(do" and the closing ")". I tried his suggested, though as yet untried fix of grouping each section in its own set of "(do" nests like so:
(do (do (TrackSelect 1 -1) (all of the functions in this group) ) (do (another group of functions) ) (do (another group) ) (do (and the same for all 11 groups) ) )
And so on and so on for all 11 groups. It ran without error. Now the program was made up of only 11 functions instead of 261 functions. It just so happened that each of those 11 functions nested between 8 and 15 other functions, but now CAL didn't care. It wasn't a matter of how many functions total there were, just how many nests there were. To further address this issue, I removed all of those "(do" nests and then counted 255 functions before adding the "(do". It still worked. I moved the "(do" one more function down to go between the 256th and 257th and the error returned. So long as only 256 functions were present, the 255 functions plus the one "(do" function nesting the rest of them, everything was cool and the program ran. There is likely a limit to the number of nested functions that a CAL program can deal with in this way also, thus putting an upper limit on the total number of functions of any configuration that CAL will tolerate, but I have not been able to find that limit. I do know that CAL version 3 will only allow 10 nesting levels (nests within nests within nests) and that Windows 32 bit versions will accept more than this before giving an error. Otherwise, the true limits of CAL are there for you to explore. Send me an email if you find another boundary and I will add the info to the Updates page.
I have several versions of Cakewalk on the same computer. Notably, I have version 3, version 6 and version 7. As a bit of background, I started out with version 3 on a 386 running Windows for Workgroups 3.11 and later installed it on a 486 running the same version of Windows. Over time, I installed Windows 95 release A on that 486. Around December of 1997, I obtained a copy of version 6.01 and installed it into another directory of the 486 and ran both versions so I could compare features as part of writing this CAL tutorial. I remember noticing that the CAL editor of version 6 lacked the ability to perform "bracket kissing" when the opening bracket was off the screen. I was a bit disappointed with this because I found the feature very useful in debugging CAL code. This aside, I soon after upgraded to a Pentium and Windows 95 release B. Again, I installed both copies of Cakewalk on my system, first version 3 and then version 6. One day, I went back to version 3 to do use the fully operational bracket kissing feature on a CAL program I was troubleshooting, and IT WAS GONE!!! That is to say that the feature in version 3 now works exactly like it does in version 6. Somehow, version 6 has "contaminated" version 3 to the point that version 3 has started taking on some of version 6's characteristics.
I don't know how this happened, but I tried it on another Pentium I have in the bedroom and sure enough, as soon as I installed version 6, version 3 lost bracket kissing. I tried uninstalling version 6 and reinstalling version 3 but the contamination remains to this day on both computers. Even now that I have installed version 7 beta on one of them with bracket kissing restored, I still have the problem with version 3. I have also tried installing version 3 on other other 386 and 486 computers running Windows 3.11 and everything is fine. I have installed on another 486 running Windows 95 release A and have full bracket kissing. I even installed it on a Pentium Pro running Windows 95 release B and had no trouble. I then installed version 6 on the Pentium Pro system and version 3 was COMPLETELY UNAFFECTED!! Go figure!
This may seem like a small thing to be worried about, but let me say that the bracket kissing phenomenon is the only contamination effect I HAVE NOTICED. There may be other subtle changes that I haven't noticed. I don't know how likely it is that this "cross contamination" would happen on your or any other system, but at least be aware that if you have multiple versions of Cakewalk on the same computer, odd things might happen.
This is going to be a small section in that I have only noticed one bit of odd behavior running a CAL program on a MIDI track and then having some strange effect on digital audio tracks. I have noticed in version 8 that allot of things will cause the audio effects RETURN knobs to reset to the full OFF position if you fail to take an opening "snapshot" at the start of a project. Among these causes is running CAL on a MIDI track with the Console View open even if that view does not have focus (its blue Title Bar is gray). It doesn't happen all of the time, just like closing a project and reopening it doesn't always move that knob to OFF, but sometimes it will. I can't lock down any repeatable pattern, it just happens once in a while. If you ever run a CAL program and after starting playback notice that your audio tracks have gone dry, perhaps the aux1 RETURN knob has been reset. I have made it a habit to record all audio Console View and effects settings in the song's INFO page and keep it updated as I edit. It's good to also include any settings for your audio card mixer or outboard mixer too.
When I introduced the "switch" function in the Branching Functions section, I touched on the idea of using a "switch" tree to control a menu. The execution of this concept is very simple. Look at this example:
(int mode 0) (getInt mode "Select Program You Wish To Run " 0 3) (switch mode 0 (do (a bunch of stuff that makes up a CAL program) ) 1 (do (another program the user could run) ) 2 (do (you get the idea) ) mode (do (the last of the program selections) ) )
In this example we have used "switch" as a menu driver. The user selects which of four CAL programs they wish to run and the tree picks out the right one based on the value of "mode". Did you notice the "case" for the last selection? Why didn't I use a 3 instead? Well, CAL doesn't provide an "if all else fails, do this" sort of trap, so we must make our own. Because "mode" will always equal "mode" no matter what, if the "switch" tree didn't find a match for "mode" among the cases provided, it will always run the last case. This isn't so important in this example because our "getInt" function sets limits on the values we could enter for "mode", and so it could never be a number other than 0, 1, 2 or 3. As such, we could have used a 3 instead of "mode" as our last case. However, what about this example:
(forEachEvent (if (== Event.Kind NOTE) (switch Note.Key 35 (+ Note.Key 1) 38 (+ Note.Key 2) 49 (+ Note.Key 8) Note.Key (- Note.Vel 10) ) )
For this example, suppose we have a general MIDI percussion track selected. Here we start a "forEachEvent" loop and filter out everything except note events using the "if". Now we set up a "switch" tree that performs a special change on certain notes, in this case it changes Acoustic Bass drum notes (note number 35) to Bass Drum #1 notes by adding a 1 to the note number. If the loop finds an Acoustic Snare note (#38), it adds 2 to it and it becomes an Electric Snare note. Any Crash Cymbal #1 notes (#49) it finds are changed to Crash Cymbal #2 notes, and everything else, including native Bass Drum #1, Electric Snare and Crash Cymbal #2 notes are lowered in velocity by 10 units to make the changed notes stand out a bit. As you can see, in this case we needed an "if all else fails" trap so we don't have to list a case for every single note in the drum kit.
As odd as it may seem, there is a way to provide the user of your CAL programs a set of simple instructions that they can look at or not as they feel necessary. The way this is done is through the use if a "while" loop enclosing a group of "pause" dialogs. One very useful reason to employ this technique is due to the implementation of presets in a program. In this way, the user can choose from one of several presets that auto-configure the program for specific operations, choose to enter all parameters by hand or choose to view a series of "Help" boxes that describe the presets and offer other pertinent information. Here is an example of some code that will generate the help boxes.
(int preset -1) ;Declare the preset variable.
(while (== preset -1) ;So long as "preset" equals -1...
(do ;do the following, starting with a request for user input.
(getInt preset "Select Preset Mode or Hit ENTER To Review A List Of Presets " -1 7)
(if (== preset -1) ;If the default value is still in "preset"...
(do ;display the following "pause" boxes.
(pause "Note Qualification Preset List : 0=Set All Variables By Hand 1=Spot Apply")
(pause "MONOPHONIC TRACK PRESETS 2=Quarter Notes or Bigger 3=All Long Notes")
(pause "4=Only First Note in a Phrase 5=Only Last Note in a Phrase")
(pause "POLYPHONIC TRACK PRESETS 6=All Notes 7=Only Last Note of Runs")
) ;Close the "do" nesting the "pause" statements.
) ;Close the "if".
) ;Close the "do" nesting the loop functions.
) ;Close the "while" loop.
In the first line, we declare a variable that we will use as our preset selection variable. It is initialized to -1 so that in the input dialog that follows, the user can simply hit ENTER to select this default value and thus read the "Help" boxes. After declaring "preset", we start a "while" loop that continues to loop for as long as "preset" is -1. Next is that input dialog where the user can enter a preset number from 0 to 7 or keep the default value of -1 and read the "Help". The "if" function triggers if the value of "preset" is still -1. If is isn't, then the "if" is bypassed and we return to the top of the "while" loop where the condition for the loop fails and execution immediately proceeds with the line of code after the "while" loop's closing bracket. If "preset" is still -1, then the "if" condition passes and we execute its nested functions which consist of a series of "pause" statements containing some useful information in their text arguments. The user reads this information, and from it decides which preset they wish to select. After the last "pause" dialog box, the "if" exits and we come back to the top of the "while" loop. The variable "preset" still equals -1, so we start the loop again. This time, the user will decide on a preset based upon what they read in the "pause" boxes. The value is entered during the "getInt" statement. The "if" now fails because "preset" has been set to a value other than -1. The "while" loop likewise fails for the same reason and the program continues, likely with a "switch" tree to process the user's selection in the "preset" variable. This fragment of code is taken from one of the sample programs called CROSFADE.CAL. Look it over to see this "On-Line Help" technique as well as get ideas for building a switch tree to process the preset selection.
If you have been using Cakewalk version 6 and now version 7, you are aware of the RPN and NRPN events. These events are multiple CONTROL events that have been collected into a single event for compactness and other system reasons. The drawback is that most of us are used to working with the controller events "as is" and have no desire to learn some obnoxious formula to convert RPN events back and forth when we need to view or edit them. The best example I can think of is the way Cakewalk will take the three controller events that make up a pitch bend depth setting message and stuff them into one RPN event. What if you want to change the bend depth on that track? How do you change this RPN event to reflect the change in the value byte of the third controller event? Well, without a calculator and a few minutes of valuable time, you can't. What's more, if you have CAL programs that read and/or change this bend depth information like I do, the programs are now useless! I'm sorry, but I refuse to take this lying down. There is a way around the problem.
By adding a statement to the TTSSEQ.INI file in the CAKEWALK directory, we can instruct Cakewalk not to generate RPN and NRPN events. Open TTSSEQ.INI in the Notepad editor and locate the heading [OPTIONS]. Under this heading add the line,
and save the file. Now restart Cakewalk and the RPN event will never darken your sequence again! Note that if your sequence has native RPN events already in it, they will appear as such. This change doesn't force Cakewalk to decompile RPN events that are present in a sequence, just prevents it from generating them out of clusters of related controller events. For those who want to keep the RPN event and still want to change bend depth from the Event View, see the section on RPN Events for a chart of RPN values that correlate to pitch bend depth values.
Something very odd happens when you send some event parameters negative or over their allowed limits. The effect is referred to in computer circles as "aliasing". What this means is the value assigned to the variable will be something that appears unrelated to the actual value that should have been stored there. For example, suppose in the act of scaling a note velocity, the value goes negative. This negative value is then entered into the sequence as the note's velocity even though it's impossible for a note to have a negative velocity. Now you look at that note in the EVENT VIEW and notice that its value is indeed a positive number, but seemingly unrelated to the results of the equation that generated it. The value has been "aliased". Because the "pocket" for a velocity value cannot hold a negative number, the "negativeness" of the value has been stripped away and the remaining bits that made up the negative number were left behind as a bogus positive number. Even though most of the time attempting to place a number that is too large for a variable will result in an error message, driving a result negative will often go unnoticed by CAL. To prevent aliasing and over range error messages, it is important to add over range checking to any calculation that can result in a variable going over or under allowable limits. There are examples of this in the sample programs, but for the sake of time, I'll show you what I mean. Let's say we're scaling velocities. At the end of the scaling formula simply add:
(some calculation that scales velocities) (if (< velocity 0) (= velocity 0)) ;Correct negative velocities. (if (> velocity 127) (= velocity 127)) ;Correct velocities that overrange. (and go on with your program)
These two extra lines of code test for, in the first, the variable "velocity" being less than zero, in other words, a negative number. If this is found to be so, "velocity" is set equal to zero. In the second, if "velocity" is greater than 127, then it is set equal to 127. This way, no matter what result your scaling formula generates, the value ultimately assigned to "velocity" will be within the allowable range for that parameter.
If you are going to use CAL allot, it might be a good idea to look into constructing a library of CAL modules that perform specific "micro" tasks and use them as building blocks to make larger CAL programs. You can also build slightly different versions of a basic program that each perform the same task but with different preset parameters or focus. The only real limitation to this system is not to use "include" within a loop like "forEachEvent" or "while". This is because each time the loop is run, CAL has to go searching for the included program, load it and run it. Even with the included program residing in the disk cache memory, this can be a very time consuming process and will slow down execution of your parent CAL program considerably. The included programs can, themselves, contain such loops without degradation of performance.
If you're going to make this idea work, you must be organized about it. Decide on some conventions and stick to them. One convention might be to make sure all variables your included programs declare are named fundamentally different from the way you name variables in parent programs. This will prevent any "Variable redefined" errors from popping up at run time. For example, make sure all parent variables you declare are entirely in lower case and those in all included programs are declared with a capital letter at the beginning or the end, or all included programs use a prefix like "inc" when declaring variables. This way, the parent program can safely declare ""pitch" knowing any similar variable in an included program will be declared as "Pitch" or "pitcH" or "incpitch" or whatever. Also, any variable declared in an included program whose value will not be needed by the parent should be erased using the "undef" function at the end of the included program so as not to occupy memory any longer than necessary.
Passing data back and forth between parent programs and their included child programs should follow a set protocol. Keep names of variables consistent by function so that as you write a new parent, it will easily dovetail into your current scheme. Use the same structure for assembling data to be passed to any included program and collecting returned data. The above section on using switch trees to select programs from a menu could be expanded to using a switch tree to select included programs provided all such programs use a rigid data passing protocol. One thing I do is to have my CAL programs interpolate all events to a given MIDI channel before doing something that adds a bunch of, say, controller events to a sequence. I then make sure the program adds the new events on the channel just above the one I used for the interpolation. As a result, all CAL programs I include or may call later can tell the native events from the new events so they can be accessed in whatever way separately. This also makes it easy to view and alter the separate groups by hand should that be desirable.
There is one other consideration worth mentioning. An "include" function will use up a nesting level on its own, not to mention any nesting levels created by the included program. CAL can handle only so many nesting levels before issuing an "Evaluation stack overflow" error. Keep included programs very efficient and don't run "include" functions if they themselves will be nested very deep within a function of a parent program.