CROSFADE.CAL

The text of this program is different than you would use in a real CAL program because of the addition of "nesting indention level" numbers at the start of each line of code. The reason they are there is because:

  1. HTML has limited ability to translate indention in normal fonts and so in order to demonstrate indention, that empty space has to be filled with something.
  2. With the addition of the red nesting level numbers, it is easy to see the nesting level of each line of code, see how the THEN and ELSE parts of an "if" function line up and how the closing brackets line up with the corresponding opening ones.

As far as the comment lines, they all follow the rules for comments in code because each comment line is preceded with a semi-colon. The colors serve to distinguish the nature of the comments. The blue comments explain what the code is doing in that section. The green ones denote the specific action being taken by the next line of code as a way of keeping strait the steps being taken within a larger blue-commented section.


Program Text for CROSFADE.CAL


 

;Crosfade.cal version 1.0 - An A to B Source Crossfade Program for Cakewalk (c) 1997 by D. Glen Cardenas

;

;PURPOSE

;

;This program causes a volume level crossfade to take place in real time between the sounds being

;generated on any two tracks during playback. This crossfade takes place starting at the beginning of a

;target note and progresses through the voicing of the note from an initial mix level to a climax mix level at

;a specified attack rate, and then can also be followed by a reverse crossfade from the climax level to a

;concluding level at a defined decay rate. This crossfade takes place all within the span of the note, and,

;for short notes, can occure at high speeds or for longer notes can be drawn out as long as desired. The

;idea behind this program is to be able to "slide" between two distint sounds and then back again during

;the voicing of a note in a way that is repeatable for all target notes that fit the user's defined criteria

;for duration and position relitive to the other notes around it. Special effects and the tasteful introduction

;of reed breath sounds into accent and phrase starting and/or ending notes are the primary uses for this

;program.

;

;INSTRUCTIONS

;

;------------------------- END OF DOCUMENTATION --------------------------

;

;---------------------------START OF CODE BLOCK------------------------------

;All CAL programs start with an opening "(do" statement.

0 (do

;First we call the (include) function with NEED40.CAL as the argument. This is to be sure that we are

;running a level of CAL high enough to support the functions and techniques we will be using in this

;program.

0 1 (include "need40.cal")

;We also include TIMERROR.CAL to test for non-sequential events.

0 1 (include "timerror.cal")

;Let's set the variables. This program is a rather complex little puppy and uses lots of variables. We will

;group them into various sections for clarity.

;The first group are the variables that make up the presets.

0 1 (int preset -1)

0 1 (int pre 0)

0 1 (int post 0)

0 1 (int length 0)

0 1 (int smix 0)

0 1 (int mmix 90)

0 1 (int emix 0)

0 1 (int atime 0)

0 1 (int dtime 0)

;This group are variables which must be entered by the user each time the program runs.

0 1 (int vola 100)

0 1 (int volb 100)

0 1 (int fader 100)

0 1(int track 1)

0 1 (int chan 1)

;These are later calculated using the value of "chan".

0 1 (int chana 0)

0 1 (int chanb 0)

0 1 (int chanc 0)

;This group are the buffers that hold working values based on the global variables above.

0 1 (int decay 0)

0 1 (int vas 0)

0 1 (int vbs 0)

0 1 (int vam 0)

0 1 (int vbm 0)

0 1 (int vae 0)

0 1 (int vbe 0)

;We use these variables to test the notes being scanned for compliance to user criteria for inclusion.

0 1 (int bypass 0)

0 1 (dword prerest 0)

0 1 (dword postrest 0)

;These variables are use during the actual crossfading loop.

0 1 (int alast 0)

0 1 (int blast 0)

0 1 (int looptime 0)

0 1 (int da 0)

0 1 (int db 0)

;These are the volume event values that are inserted into the sequence.

0 1 (int newa 0)

0 1 (int newb 0)

;Loop and insertion control is handled by these variables

0 1 (int percent 0)

0 1 (word gap 0)

0 1 (word gapper 0)

;There are three note parameter arrays in this program. This is because we must be able to calculate the

;gap between the target note and both the note before and after it. However, sometimes we are only

;going to use two notes and sometimes only one note in the evaluations, so we may or may not need

;data in all three arrays. We make this choice at the start of the (forEachEvent) loop. In any event, we

;declare the components of all three arrays.

;this array is the last to be filled and holds the note before the target note.

0 1 (int n2note 0)

0 1 (int n2vel 0)

0 1 (word n2dur 0)

0 1 (dword n2time 0)

;this array holds the target note for a 3 note calculation or the note before the target for a 2 note calculation

0 1 (int n1note 0)

0 1 (int n1vel 0)

0 1 (word n1dur 0)

0 1 (dword n1time 0)

;this array holds the note after the target

0 1 (int tnnote 0)

0 1 (int tnvel 0)

0 1 (word tndur 0)

0 1 (dword tntime 0)

;These are the working variables that the target note will occupy during calculations.

0 1 (int notenote 0)

0 1 (int notevel 0)

0 1 (word notedur 0)

0 1 (dword notetime 0)

;We can tell the user about the presets that pre-configure some parameters for commonly used features.

;So the user can view a list of presets, we use a (while) loop. As long as "preset" = -1, we will scroll

;through the option (pause) screens and then loop back to the (getInt) statement where they enter the

;desired preset value. If the user just hits "ENTER", then the default value of -1 remains and we again

;scroll through the options. When the user finely decides on an option and enters that number, we bypass

;the (pause) screens and exit the (while) loop.

;For so long as the user has not made a selection...

0 1 (while (== preset -1)

;do the following...

0 1 2 (do

0 1 2 3 (getInt preset "Select Preset Mode or Hit ENTER To Review A List Of Presets " -1 7)

;If the user wants to view the preset options....

0 1 2 3 (if (== preset -1)

;do the following.

0 1 2 3 4 (do

0 1 2 3 4 5 (pause "Note Qualifacition Preset List : 0=Set All Variables By Hand 1=Spot Apply")

0 1 2 3 4 5 (pause "MONOPHONIC TRACK PRESETS 2=All Quarter Notes or Bigger 3=Only Long Notes ")

0 1 2 3 4 5 (pause "4=Only First Note in a Phrase 5=Only Last Note in a Phrase ")

0 1 2 3 4 5 (pause "POLYPHONIC TRACK PRESETS 6=All Notes 7=Only Last Note of Runs")

;Close the "(do)".

0 1 2 3 4 )

;Close the "(if)".

0 1 2 3 )

;Close the "(do)" holding the code for the (while) loop.

0 1 2 )

;Close the (while) loop.

0 1 )

;Now that we have a user selection, we use a (switch) function to execute only that code associated with the preset.

0 1 (switch preset

0 1 0

;The first choice is to enter all needed data by hand. Here we enter criteria for distance between the

;target note and the notes before and after it, the minimum length of the target note, the mix levels, and

;the attack and decay times.

0 1 2 (do

0 1 2 3 (getInt bypass "Nature Of Track - 0=Monophonic 1=Polyphonic " 0 1)

0 1 2 3 (if (== bypass 0)

0 1 2 3 4 (do

0 1 2 3 4 5 (getInt pre "Minimum Pre-Note Rest In 16th Notes (0=Ignore) " 0 16)

0 1 2 3 4 5 (getInt post "Minimum Post-Note Rest In 16th Notes (0=Ignore) " 0 32)

0 1 2 3 4 )

0 1 2 3 4 (do

0 1 2 3 4 5 (= post 1)

0 1 2 3 4 5 (getInt post "Minimum Distance in 16ths to Next Note on Track " 1 32)

0 1 2 3 4 5 (= post (- 0 post))

0 1 2 3 4 )

0 1 2 3 )

0 1 2 3 (getInt length "Minimum Length Of Note In 16ths (0=Ignore) " 0 64)

0 1 2 3 (getInt smix "Starting Mix Percentage Of Source A " 0 90)

0 1 2 3 (getInt mmix "Midpoint Mix Percentage Of Source A " 10 100)

0 1 2 3 (getInt emix "Ending Mix Percentage Of Source A " 0 90)

0 1 2 3 (getInt atime "Number Of 16th Notes Of Attack Mix Time " 0 64)

0 1 2 3 (getInt dtime "Percentage Remainder Of Note For Decay Mix " 0 100)

0 1 2 )

;There are a few presets already configured for commonly used functions.

;To spot apply the effect, we ignore pre and post note gaps and length.

0 1 1

0 1 2 (do

0 1 2 3 (= pre 0)

0 1 2 3 (= post 0)

0 1 2 3 (= length 0)

0 1 2 3 (= smix 0)

0 1 2 3 (= mmix 100)

0 1 2 3 (= emix 0)

0 1 2 3 (= atime 3)

0 1 2 3 (= dtime 20)

0 1 2 )

;For quarter or bigger notes, the pre and post are ignored and length is 2.

0 1 2

0 1 2 (do

0 1 2 3 (= pre 0)

0 1 2 3 (= post 0)

0 1 2 3 (= length 2)

0 1 2 3 (= smix 0)

0 1 2 3 (= mmix 100)

0 1 2 3 (= emix 0)

0 1 2 3 (= atime 3)

0 1 2 3 (= dtime 20)

0 1 2 )

0 1 3

;Only notes longer than a half note.

0 1 2 (do

0 1 2 3 (= pre 0)

0 1 2 3 (= post 0)

0 1 2 3 (= length 8)

0 1 2 3 (= smix 0)

0 1 2 3 (= mmix 100)

0 1 2 3 (= emix 0)

0 1 2 3 (= atime 7)

0 1 2 3 (= dtime 50)

0 1 2 )

;First phrase notes.

0 1 4

0 1 2 (do

0 1 2 3 (= pre 8)

0 1 2 3 (= post 0)

0 1 2 3 (= length 4)

0 1 2 3 (= smix 0)

0 1 2 3 (= mmix 100)

0 1 2 3 (= emix 0)

0 1 2 3 (= atime 2)

0 1 2 3 (= dtime 25)

0 1 2 )

;Last phrase notes

0 1 5

0 1 2 (do

0 1 2 3 (= pre 0)

0 1 2 3 (= post 8)

0 1 2 3 (= length 4)

0 1 2 3 (= smix 0)

0 1 2 3 (= mmix 100)

0 1 2 3 (= emix 0)

0 1 2 3 (= atime 2)

0 1 2 3 (= dtime 25)

0 1 2 )

;All poly notes.

0 1 6

0 1 2 (do

0 1 2 3 (= pre 0)

0 1 2 3 (= post -2)

0 1 2 3 (= length 4)

0 1 2 3 (= smix 0)

0 1 2 3 (= mmix 100)

0 1 2 3 (= emix 0)

0 1 2 3 (= atime 1)

0 1 2 3 (= dtime 20)

0 1 2)

;last notes of runs.

0 1 preset

0 1 2 (do

0 1 2 3 (= pre 0)

0 1 2 3 (= post -8)

0 1 2 3 (= length 4)

0 1 2 3 (= smix 0)

0 1 2 3 (= mmix 100)

0 1 2 3 (= emix 15)

0 1 2 3 (= atime 3)

0 1 2 3 (= dtime 50)

0 1 2 )

0 1 )

;The presets don't give all of the data needed to run the program. Here volume level information is entered.

;The "full volume" variables are the amount of each track's total volume that the mix percentage has access to.

;Both "vola" and "volb" can be up to twice the normal value so that higher final mix levels can be achieved.

0 1 (getInt vola "Full Volume Level For Source A " 10 255)

0 1 (getInt volb "Full Volume Level For Source B " 10 255)

0 1 (= fader vola)

0 1 (getInt fader "Current Fader Setting For Source A " 10 127)

;Here we input the MIDI channel for "chan", correct its value, use it to calculate "chana" and "chanb",

;input the track number for the secondary track and correct its number also.

0 1 (getInt chan "MIDI Channel For Track Of Source A " 1 16)

0 1 (-- chan)

0 1 (= chana (+ chan 1))

0 1 (if (== chana 16) (= chana 0))

0 1 (= chanb (+ chana 1))

0 1 (if (== chanb 16) (= chanb 0))

0 1 (= chanc (+ chanb 1))

0 1 (if (== chanc 16) (= chanc 0))

0 1 (getInt track "Track Number To Create Source B " 1 256)

0 1 (-- track)

;We convert 16th note variable values to ticks using the constant TIMEBASE.

0 1 (= length (/ (* length TIMEBASE) 4))

0 1 (= pre (/ (* pre TIMEBASE) 4))

0 1 (= post (/ (* post TIMEBASE) 4))

0 1 (= atime (/ (* atime TIMEBASE) 4))

0 1 (message "Forcing All Events To Channel " (+ chan 1))

;To avoid any confusion later when sorting events for moving to the secondary track, and to make sure any

;native volume events are on "chan" for later integrating, we interpolate all events to "chan".

;Reset the interpolate Search filter to include everything.

0 1 (ResetFilter 1 1)

;Reset the interpolate Replace filter to include everything.

0 1 (ResetFilter 2 1)

;Set Replace filter to force events to "chan".

0 1 (SetFilterRange 2 10 1 chan chan)

;This fixes a bug in version 6.

0 1 (= From From)

0 1 (= Thru Thru)

;Do the interpolation

0 1 (EditInterpolate 1)

;We call another CAL program that removes zero-length notes because a value of zero in a note's duration

;is used as a marker to indicate the lack of the presents of a note in a storage cell.

0 1 (include "zerokill.cal")

;This next included program searches the section of the track before the selected range to see if there have

;been any updates to the value of "fader" (the normal volume level of the track) in case the user has made

;changes to the mix that need to be taken into account before proceeding.

;We pass to the included program the type of controller we want to search for. In this case, volume events.

0 1 (= preset 7)

;Now search back and see if there is an updated value we should be using for the track volume.

0 1 (include "srchback.cal")

;And now the processing loop

0 1 (forEachEvent

;If the current event is a note......

0 1 2 (if (== Event.Kind NOTE)

;do the following:

0 1 2 3 (do

0 1 2 3 4 (message "Processing Note At Time Index " Event.Time)

;First we must shuffle the stored notes down to the next step on the ladder. If there are no notes stored in

;some or all of the slots then it doesn't matter, so we might as well do this in any event.

;first the contents of the "n1" buffers are moved to the "n2" buffers.

0 1 2 3 4 (= n2time n1time)

0 1 2 3 4 (= n2note n1note)

0 1 2 3 4 (= n2vel n1vel)

0 1 2 3 4 (= n2dur n1dur)

;Now the contents of the "tn" buffers are moved to the "n1" buffers

0 1 2 3 4 (= n1time tntime)

0 1 2 3 4 (= n1note tnnote)

0 1 2 3 4 (= n1vel tnvel)

0 1 2 3 4 (= n1dur tndur)

;Finely the current note parameters are moved to the "nt" buffers

0 1 2 3 4 (= tntime Event.Time)

0 1 2 3 4 (= tnnote Note.Key)

0 1 2 3 4 (= tnvel Note.Vel)

0 1 2 3 4 (= tndur Note.Dur)

;We reset "bypass" to get ready for judging this new note.

0 1 2 3 4 (= bypass 0)

;This is the first test for "bypass". Simply put, if we are requesting either a pre-note rest or a post-note rest

;evaluation, then we will perform the following tests.

0 1 2 3 4 (if (|| (!= post 0) (!= pre 0))

0 1 2 3 4 5 (do

;If we are going to judge both rest conditions, then we must have all three slots filled with notes. If "n2dur"

;is zero, then the last slot is still empty and we cannot yet do the judging. We bypass this note, shuffel the

;notes in their slots during the next pass, and then try again.

0 1 2 3 4 5 6 (if (&& (&& (!= pre 0) (!= post 0)) (== n2dur 0)) (= bypass 1))

;Likewise, if only one of the rest criteria is being judged, we must have at least up to note "n1" stored, and

;so we make the test for its presents. Again, if the slot is empty, we wait for another pass.

0 1 2 3 4 5 6 (if (&& (|| (!= pre 0) (!= post 0)) (== n1dur 0)) (= bypass 1))

;In the event we have not yet disqualified this pass using the tests above, we will need to transfer the

;various notes stored in the slots into the working variables for use in the program. All of this may be of

;no consequence if we ultimately bypass this round, but if that happens, what we do here wouldn't matter

;so we might as well do it just in case we get a GO for this pass.

0 1 2 3 4 5 6 (if (!= post 0)

0 1 2 3 4 5 6 7 (do

;If we will be using post-note rest judging, then this means all three slots must be valid. We then calculate

;the distance between the target note in "n1" and the note that came before it in "n2" as well as the

;distance between "n1" and the note that follows it in "tn". Here we determine the raw time for the end

;of the previous note and the start of the following note.

0 1 2 3 4 5 6 7 8 (= prerest (+ n2time n2dur))

0 1 2 3 4 5 6 7 8 (= postrest tntime)

;Having done this, we now transfer the notes stored in the slots into the working variables. Thus, "n1"

;values are passed to the target note variable set called "note".

0 1 2 3 4 5 6 7 8 (= notetime n1time)

0 1 2 3 4 5 6 7 8 (= notenote n1note)

0 1 2 3 4 5 6 7 8 (= notevel n1vel)

0 1 2 3 4 5 6 7 8 (= notedur n1dur)

0 1 2 3 4 5 6 7 )

;As the ELSE part of the above test, we know that in order to even be in this section of code either one

;or both of the pre/post values must be present. If the above test for "post" fails, then "pre" must be a

;valid test value and therefore we can assume that it is the only test we will perform. To witt, we need only

;concern ourselves with the first two slots, "n1" being the previous note, and "tn" the target note. Here

;we calculate the distance between the target note and the end of the previous note, then move the "tn"

;values into the "note" working variables.

0 1 2 3 4 5 6 7 (do

0 1 2 3 4 5 6 7 8 (= prerest (+ n1time n1dur))

0 1 2 3 4 5 6 7 8 (= notetime tntime)

0 1 2 3 4 5 6 7 8 (= notenote tnnote)

0 1 2 3 4 5 6 7 8 (= notevel tnvel)

0 1 2 3 4 5 6 7 8 (= notedur tndur)

0 1 2 3 4 5 6 7 )

0 1 2 3 4 5 6 )

;Now for each of the possibilities of note rest testing, we check to see of the conditions are met.

0 1 2 3 4 5 6 (if (&& (!= pre 0) (< notetime (+ prerest pre))) (= bypass 1))

0 1 2 3 4 5 6 (if (&& (> post 0) (> (+ notetime notedur) (- postrest post))) (= bypass 1))

0 1 2 3 4 5 6 (if (&& (< post 0) (> notetime (+ postrest post))) (= bypass 1))

0 1 2 3 4 5 )

;This concludes the rest condition "bypass" setup testing. As the ELSE to the condition for there being

;either pre or post note rest conditions, we have a section to account for no such testing and therefore no

;need for any rests or note storage. Thus, the only slot used is the top one and its values are transfered

;to the working variables.

0 1 2 3 4 5 (do

0 1 2 3 4 5 6 (= notetime tntime)

0 1 2 3 4 5 6 (= notenote tnnote)

0 1 2 3 4 5 6 (= notevel tnvel)

0 1 2 3 4 5 6 (= notedur tndur)

0 1 2 3 4 5 )

0 1 2 3 4 )

;The only test left is to see if the target note is long enough to qualify. This test is made regardless of any

;other considerations.

0 1 2 3 4 (if (< notedur length) (= bypass 1))

;The crossfade function is now performed so long as none of the above tests failed, setting "bypass" to 1.

0 1 2 3 4 (if (== bypass 0)

0 1 2 3 4 5 (do

;First we clone the target note for the secondary track.

0 1 2 3 4 5 6 (insert notetime chanb NOTE notenote notevel notedur)

;Now we take the mix percentages and use them to convert the max level variables into a final volume

;level. If the resulting value is out of range, it is corrected to 127. This can happen due to the fact that

;the max volume levels can go as much as twice the normal limit to allow blending of A and B.

0 1 2 3 4 5 6 (= vas (/ (* vola smix) 100))

0 1 2 3 4 5 6 (if (> vas 127) (= vas 127))

0 1 2 3 4 5 6 (= vbs (/ (* volb (- 100 smix)) 100))

0 1 2 3 4 5 6 (if (> vbs 127) (= vbs 127))

0 1 2 3 4 5 6 (= vam (/ (* vola mmix) 100))

0 1 2 3 4 5 6 (if (> vam 127) (= vam 127))

0 1 2 3 4 5 6 (= vbm (/ (* volb (- 100 mmix)) 100))

0 1 2 3 4 5 6 (if (> vbm 127) (= vbm 127))

0 1 2 3 4 5 6 (= vae (/ (* vola emix) 100))

0 1 2 3 4 5 6 (if (> vae 127) (= vae 127))

0 1 2 3 4 5 6 (= vbe (/ (* volb (- 100 emix)) 100))

0 1 2 3 4 5 6 (if (> vbe 127) (= vbe 127))

;The percentage of decay is converted into ticks.

0 1 2 3 4 5 6 (= decay (/ (* (- notedur atime) dtime) 100))

;If the attack time is not zero, we perform the attack phase of the crossfade.

0 1 2 3 4 5 6 (if (!= atime 0)

0 1 2 3 4 5 6 7 (do

;We reset our transient variables for a new pass.

0 1 2 3 4 5 6 7 8 (= gap 0)

0 1 2 3 4 5 6 7 8 (= percent 0)

;Here we calculate the percentage each gap is of the total time with a minimum of 2 percent being allowed.

0 1 2 3 4 5 6 7 8 (while (< percent 2)

0 1 2 3 4 5 6 7 8 9 (do

0 1 2 3 4 5 6 7 8 9 10 (++ gap)

0 1 2 3 4 5 6 7 8 9 10 (= percent (/ (* gap 100) atime))

0 1 2 3 4 5 6 7 8 9 )

0 1 2 3 4 5 6 7 8 )

;The starting levels of both channels are forced at the beginning of the note.

0 1 2 3 4 5 6 7 8 (insert notetime chana CONTROL 7 vas)

0 1 2 3 4 5 6 7 8 (insert notetime chanb CONTROL 7 vbs)

;Loop variables are set and the amount of volume change is calculated.

0 1 2 3 4 5 6 7 8 (= alast vas)

0 1 2 3 4 5 6 7 8 (= blast vbs)

0 1 2 3 4 5 6 7 8 (= da (- vam vas))

0 1 2 3 4 5 6 7 8 (= db (- vbm vbs))

0 1 2 3 4 5 6 7 8 (= looptime gap)

;Here is the loop.

0 1 2 3 4 5 6 7 8 (while (< looptime atime)

0 1 2 3 4 5 6 7 8 9 (do

;First the new volume levels are calculated for this pass of the loop based on the ongoing change in the

;remainder percentage of time left in the attack.

0 1 2 3 4 5 6 7 8 9 10 (= newa (+ vas (/ (* percent da) 100)))

0 1 2 3 4 5 6 7 8 9 10 (= newb (+ vbs (/ (* percent db) 100)))

;These new volume levels are inserted providing they are not redundant of either the opening level or the

;value inserted during the previous loop.

0 1 2 3 4 5 6 7 8 9 10 (if (&& (!= newa vam) (!= newa alast)) (insert (+ notetime looptime) chana CONTROL 7 newa))

0 1 2 3 4 5 6 7 8 9 10 (if (&& (!= newb vbm) (!= newb blast)) (insert (+ notetime looptime) chanb CONTROL 7 newb))

;The loop variables are updated.

0 1 2 3 4 5 6 7 8 9 10 (= alast newa)

0 1 2 3 4 5 6 7 8 9 10 (= blast newb)

0 1 2 3 4 5 6 7 8 9 10 (+= looptime gap)

0 1 2 3 4 5 6 7 8 9 10 (= percent (/ (* looptime 100) atime))

0 1 2 3 4 5 6 7 8 9 10 (if (&& (< post 0) (>= (+ notetime looptime) (- postrest 1))) (= atime looptime))

0 1 2 3 4 5 6 7 8 9 )

0 1 2 3 4 5 6 7 8 )

0 1 2 3 4 5 6 7 )

0 1 2 3 4 5 6 )

;Now that the attack loop is finished, the final mid-point volume levels are inserted.

0 1 2 3 4 5 6 (insert (+ notetime atime) chana CONTROL 7 vam)

0 1 2 3 4 5 6 (insert (+ notetime atime) chanb CONTROL 7 vbm)

;If the decay time is not zero, do the decay phase of the crossfade.

0 1 2 3 4 5 6 (if (!= decay 0)

0 1 2 3 4 5 6 7 (do

;The transient variables are reset for the decay loop.

0 1 2 3 4 5 6 7 8 (= gap 0)

0 1 2 3 4 5 6 7 8 (= percent 0)

;The gap for the decay loop is set and its percentage calculated, again with a minimum of 2 percent allowed.

0 1 2 3 4 5 6 7 8 (while (< percent 2)

0 1 2 3 4 5 6 7 8 9 (do

0 1 2 3 4 5 6 7 8 9 10 (++ gap)

0 1 2 3 4 5 6 7 8 9 10 (= percent (/ (* gap 100) decay))

0 1 2 3 4 5 6 7 8 9 )

0 1 2 3 4 5 6 7 8 )

;Loop variables are set and the amount of volume change is calculated for the decay.

0 1 2 3 4 5 6 7 8 (= alast vam)

0 1 2 3 4 5 6 7 8 (= blast vbm)

0 1 2 3 4 5 6 7 8 (= da (- vae vam))

0 1 2 3 4 5 6 7 8 (= db (- vbe vbm))

0 1 2 3 4 5 6 7 8 (= looptime (- notedur decay))

0 1 2 3 4 5 6 7 8 (if (&& (< post 0) (>= (+ notetime looptime) (- postrest 1)))

0 1 2 3 4 5 6 7 8 9 (do

0 1 2 3 4 5 6 7 8 9 10 (= looptime (+ atime 2))

0 1 2 3 4 5 6 7 8 9 10 (= notedur looptime)

0 1 2 3 4 5 6 7 8 9 )

0 1 2 3 4 5 6 7 8 )

0 1 2 3 4 5 6 7 8 (= gapper 0)

;And now the decay loop. It does the same thing as the attack loop except that it requires a variable called

;"gapper" to keep track of the change in time because "looptime" does not start at zero on this occasion.

;It must start at the point in the note where decay begins and thus cannot be used as-is for percentage

;calculations. "gapper" is used instead.

0 1 2 3 4 5 6 7 8 (while (< looptime notedur)

0 1 2 3 4 5 6 7 8 9 (do

0 1 2 3 4 5 6 7 8 9 10 (= newa (+ vam (/ (* percent da) 100)))

0 1 2 3 4 5 6 7 8 9 10 (= newb (+ vbm (/ (* percent db) 100)))

0 1 2 3 4 5 6 7 8 9 10 (if (&& (!= newa vae) (!= newa alast)) (insert (+ notetime looptime) chana CONTROL 7 newa))

0 1 2 3 4 5 6 7 8 9 10 (if (&& (!= newb vbe) (!= newb blast)) (insert (+ notetime looptime) chanb CONTROL 7 newb))

0 1 2 3 4 5 6 7 8 9 10 (= alast newa)

0 1 2 3 4 5 6 7 8 9 10 (= blast newb)

0 1 2 3 4 5 6 7 8 9 10 (+= looptime gap)

0 1 2 3 4 5 6 7 8 9 10 (+= gapper gap)

0 1 2 3 4 5 6 7 8 9 10 (= percent (/ (* gapper 100) decay))

0 1 2 3 4 5 6 7 8 9 )

0 1 2 3 4 5 6 7 8 )

0 1 2 3 4 5 6 7 )

0 1 2 3 4 5 6 )

;At one tick past the end of the note, the normal volumes for both channels are restored.

0 1 2 3 4 5 6 (insert (+ (+ notetime notedur) 1) chana CONTROL 7 fader)

0 1 2 3 4 5 6 (insert (+ (+ notetime notedur) 1) chanb CONTROL 7 0)

0 1 2 3 4 5 )

0 1 2 3 4 )

0 1 2 3 )

0 1 2 )

0 1 )

;Now the THRU marker must be reset to make sure it includes all of the area of the sequence that contains

;new information. It is set at 2 ticks past the end of the last note effected.

0 1 (= Thru (+ (+ notetime notedur) 2))

0 1 (message "Cloning Native Volume and Modulation Events")

;All original volume and modulation events are cloned for the secondary track.

0 1 (forEachEvent

0 1 2 (do

0 1 2 3 (if (&& (== Event.Kind CONTROL) (== Control.Num 1)) (insert Event.Time chan CONTROL 1 Control.Val))

0 1 2 3 (if (&& (&& (== Event.Kind CONTROL) (== Control.Num 7)) (== Event.Chan chan)) (insert Event.Time chanc CONTROL 7 Control.Val))

0 1 2 )

0 1 )

0 1 (message "Creating Secondary Track At Track # " (+ track 1))

;Now all events on "chanb" and "chanc" are cut out and pasted to the new secondary track.

0 1 (ResetFilter 0 1)

0 1 (SetFilterRange 0 10 TRUE chanb chanc)

0 1 (EditCut From Thru TRUE TRUE FALSE FALSE FALSE FALSE)

0 1 (EditPasteToTrack From 1 0 TRUE FALSE FALSE FALSE track)

0 1 (message "Integrating Volume Controller Events At Channel " (+ chan 1))

0 1 (= preset 7)

0 1 (include "integrat.cal")

0 1 (TrackSelect 0 -1)

0 1 (TrackSelect 1 track)

0 1 (= chan chanc)

0 1 (include "integrat.cal")

;And so the program ends.

0 )

;---------------------------END OF PROGRAM--------------------------------


Program Text for ZEROKILL.CAL


;Zerokill.cal version 1.0 - A zero length note removal program for Cakewalk (c) 1997 by D. Glen Cardenas

;

;PURPOSE

;This function removes any note with a duration of zero.

;

;INSTRUCTIONS

;Set the FROM and THRU markers and let this function run. All notes with a duration of zero will be gone.

;

;--------------------------END OF DOCUMENTATION-----------------------------

;

;---------------------------START OF CODE BLOCK---------------------------------

0 (do

0 1 (include "need20.cal")

0 1 (message "Removing Zero-Length Notes")

0 1 (forEachEvent

0 1 2 (if (&& (== Event.Kind NOTE) (== Note.Dur 0)) (delete))

0 1 )

0 )

;---------------------------------- END OF PROGRAM -------------------------

 


Program Text for SRCHBACK.CAL


0 (do

0 1 (if (!= From 0)

0 1 2 (do

0 1 2 3 (dword Hold1 From)

0 1 2 3 (dword Hold2 Thru)

0 1 2 3 (= Thru From)

0 1 2 3 (= From 0)

0 1 2 3 (message "Search-back Under Way...Stand By")

0 1 2 3 (forEachEvent

0 1 2 3 4 (if (&& (&& (== Event.Kind CONTROL) (== Control.Num preset)) (== Event.Chan chan)) (= fader Control.Val))

0 1 2 3 )

0 1 2 3 (= From Hold1)

0 1 2 3 (= Thru Hold2)

0 1 2 3 (undef Hold1)

0 1 2 3 (undef Hold2)

0 1 2 )

0 1 )

0 )

 


Program Text for INTEGRAT.CAL


;integrat.cal - An "include" program for merging overlapping controller fills (c) 1998 by D. Glen Cardenas

;

;PURPOSE

;

;This program can do a rather amazing thing. It can take two overlapping arrays of controller events and

;merge them into one solid pattern that retains the envelopes of both. For example, assume that you have

;produced a series of volume events on a track that modulates the volume so as to create a tremolo

;effect. You now have a "fill" of controller #7 events on that track that fluctuate in a smooth pattern

;from some low value to some high value and back again in a continuous pattern. Now suppose you

;want to fade that track out by slowly bringing the volume from its current fluctuating level to zero but

;without loosing the tremolo during the fade and without the tremolo's volume events causing the fading

;level to jump back to the modulating level. In other words, you have two separate sets of volume events,

;each of which are trying to set the track volume to follow their own pattern but the other pattern keeps

;overriding the first pattern's setting and vice versa. To solve this problem, you must be able to fade out

;not just the track volume, but also fade out the tremolo's modulation pattern. This program can do this

;by mathematically integrating the two controller modulation envelopes into one envelope or fill pattern.

;Thus, the track will still have fluctuating volume events producing the tremolo, but these fluctuating

;events will also all be dropping in value by the same amount over time at a rate corresponding to the

;rate of the volume events that dictated the fade. This technique can also be used to do things like double-

;modulate volume fills or produce other strange effects. You can use a program like this one to do the same

;sort of double modulation of pitch bend events with only minor changes in the code. Of course, you would

;also have to change the "calling" program accordingly to properly set up a pitch wheel version. In the case

;of INTEGRAT.CAL, it is meant to be called from a program like CROSFADE.CAL which sets up this program

;to do a very specific job, or it can be called by CNTINTEG.CAL which can set it up to act upon a wide range of

;controller events as the user sees fit.

;

;INSTRUCTIONS

;

;To run this program, it must be "included" by another program that can do the necessary setup. The most

;factor to keep in mind is that the two controller fills must, of course, have the same controller number, but

;must also be on different MIDI channels. The events on channel "chan" will be considered a reference array

;of controller events, and all controller events of the same number but on a different channel will be assumed

;to be the ones that are to be intergated into the reference fill pattern. In pratice, it doesn't really matter which

;of the two patterns is called the reference and which is considered the one to be integrated. The results will

;be the same either way. The following variables must be declared and given proper values during this setup:

;"preset" - this is the controller number that is to be integrated.

;"fader" - this is the normal, or unmodulated volume level of the track. If new normal level adjustments are

;encountered during integration, they will be integrated as well so the track's mix is preserved.

;"chan" - this is the MIDI channel designated as containing the reference fill pattern.

;"vola" - the value of the integrated result. Initialize to 0, but its value will be set by the program.

;"volb" - the starting value of the controller pattern being integrated. Can be initialized to 0

;"percent" - a working variable. Initialize to 0.

;All six of these variables must be declared and initialized by the calling program before the "include" can take place.

;------------------------- END OF DOCUMENTATION --------------------------

;

;---------------------------START OF CODE BLOCK------------------------------

0 (do

0 1 (= percent 100)

0 1 (forEachEvent

0 1 2 (if (&& (== Event.Kind CONTROL) (== Control.Num preset))

0 1 2 3 (if (== Event.Chan chan)

0 1 2 3 4 (do

0 1 2 3 4 5 (= percent (/ (* Control.Val 100) fader))

0 1 2 3 4 5 (= vola (/ (* volb percent) 100))

0 1 2 3 4 5 (if (> vola 127) (= vola 127))

0 1 2 3 4 5 (if (< vola 0) (= vola 0))

0 1 2 3 4 5 (insert Event.Time chan CONTROL preset vola)

0 1 2 3 4 5 (delete)

0 1 2 3 4 )

0 1 2 3 4 (do

0 1 2 3 4 5 (= volb Control.Val)

0 1 2 3 4 5 (= vola (/ (* volb percent) 100))

0 1 2 3 4 5 (if (> vola 127) (= vola 127))

0 1 2 3 4 5 (if (< vola 0) (= vola 0))

0 1 2 3 4 5 (insert Event.Time chan CONTROL preset vola)

0 1 2 3 4 5 (delete)

0 1 2 3 4 )

0 1 2 3 )

0 1 2 )

0 1 )

0 )

;---------------------------END OF PROGRAM--------------------------------


Demonstrations of Running CROSFADE.CAL


When this program is run, a new track is created with note events and other properties exactly like the original track. In this first example, a series of notes were placed on track 1. CROSFADE.CAL was then run with 0 for the parameter of minimum pre and post note rest and 0 for minimum note length so that all notes are selected. After running CROSFADE.CAL, track 2 was created by cloning track 1's notes and both tracks were then endowed with volume controller events that (for track 1) start each note at zero volume and then rises over a preset amount of time to full volume (as set by the program. In this example, full volume is 100 on the fader) where the level is sustained until another preset time when the volume falls from full back to 0. At the end of the note, the volume is again reset to full volume as if nothing had happened. At the start of the next note, the volume is again forced to zero and the process repeats. On the clone track, the volume starts out at full (again, 100 on the fader in this example) and falls to zero over the same time span that track 1 is rising from zero to full. Track 2's volume stays at zero until track 1 starts to drop its volume at which time track 2 rises from zero back to full volume. At the end of the note, track 2 is forced to zero volume, again, as if nothing had happened.

Figure 1 is the screen shot of track 1. Notice how the volume is forced to zero at the start of the note (the black dot at the baseline), the crossfade takes place by raising the volume, holding it there and then dropping it back to near zero. At the end, the volume is forced back to full (the spike at the end of the note).

fig. 1

Now track 2. Here the note starts at full volume, falls to zero as track 1 rises to full, stays at zero until track 1 starts to fade back out at which time the level starts back up to full. At the end, the level is quickly set back to zero. These zero events can be removed by hand (or you can create a simple CAL program to do it for you) should you want the decay of the sounds on track 2 to continue past the life of the notes. If you want this to be the normal operation of CROSFADE.CAL, then edit out the part of the code that forces the clone track to zero volume at the end of its "while" loop, or perhaps create another selectable option to allow the user to turn this feature on or off.

fig. 2

In this next example, the pre and post note filters are set to ignore any note that is less than an eighth note distance from either the note before it or after it, as well as ignoring any note less than an eighth note in duration. Figure 3 is the original track.

fig. 3

Figure 4 is the clone track. As you can see, only two notes met the conditions, so only these two notes were cloned and crossfaded. The last note should have been included, but wasn't. The very last note in range cannot be crossfaded if post note spacing is selected because there is no way to determine the distance to the next note. In the same way, if pre note spacing is selected, the very first note in a selected range cannot be crossfaded because it's distance to the note before it cannot be determined. It is therefore necessary to select a range that includes one note more at the start and end than you intend to crossfade. If you start with the very first note in a sequence, there is no way to include this note. If you select to the very last note in a sequence, it too cannot be included. The fix for this in the code is far too cumbersome to be worth the effort. Instead, it's easier to insert "dummy" notes at the start and end of the sequence to compensate for this flaw and then delete them when the crossfade program has finished running.

fig. 4

In the next two screen shots, we see the result of running the program on a polyphonic track in poly mode with all notes selected that are at least a quarter note in length. As you can see, the same conditions for the first and last notes in range being ignored hold true here but with another twist. In poly mode, a crossfade is started and continues until another note is encountered at which time the crossfade is halted and all parameters are reset to the start of a new crossfade cycle for this next note. The logic behind this system is that as a new note is voiced, the listeners attention should shift to this new note and so the crossfade starts anew for each new note as it occurrs regardless of how far along the crossfade was for a previous note. When the last note is encountered, the crossfade is aborted from the previous note and reset for this next one, but the next one is never processed. This leaves the crossfade for the previous note hanging in mid air at the start time of the last "unprocessable" note. In such cases, the results of this "blown" crossfade will have to be corrected by hand. Figure 5 is the source polyphonic track.

fig. 5

Figure 6 is the cloned track for the above poly mode example.

fig. 6

As a final example of the crossfade process, here we have the original track images for a crossfade with an attack time of zero and a decay time of 50% (figure 7), and an attack time of an eighth note and a decay of 0% (figure 8).

fig. 7 fig. 8

Figures 9 and 10 are the corresponding clone tracks to the above two examples.

fig. 9 fig. 10

A very important aspect of the crossfade program is the use of the included program INTEGRAT.CAL which has the ability of taking two separate controller patterns and performing a mathematical integration on them to form one pattern containing the overlapped characteristics of the two. For example, suppose you want to crossfade an area of a track that is fading out or is using another volume modulation effect like tremolo. If you just allowed the competing arrays of volume events fight each other, you would have a confusing mess of levels that would keep jumping from one pattern's volume commands to the other's. The only way to make these overlapping groups of volume events live together and still have both groups doing their jobs is to perform a mathematical integration of the envelopes of the two groups. This way, both groups can be combined into one cohesive pattern that performs the operations of both but without competition.