MONOPHON.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 MONOPHON.CAL


;monophon.cal version 1.0 - A lead track poly to mono converter for Cakewalk

;(c) 1997 by D. Glen Cardenas

;

;PURPOSE

;

;This program either removes any overlap of notes or causes a pre-set amount of overlap

;between notes. For those times when you are playing a lead solo track using a monophonic

;voice on a synthesizer, it can interfere with voicing to have note overlap. By entering a

;positive value for "gap", this program makes sure there is only one note voicing at a time on

;a solo track by ending any note from 0 to 30 ticks before the next note starts. If a note

;that is about to be shortened has a duration so brief that its ON time falls after the newly

;calculated OFF time, the note will be deleted. If on the other hand a set amount of overlap is

;the goal, say for a smoother transition between string notes, then entering a negative value

;for "gap" will cause notes to overlap each other by that amount. There are two modes.

;Mode 0 shortens or overlaps only notes that already overlap the next note, or in the case

;of a positive "gap", will also include notes that end no farther than "gap" amount from the

;previous note. Mode 1 sets all notes to end at the selected gap distance from the next

;note even if the note's duration must be lengthened or shortened to do so. A positive "gap"

;will space all notes by that amount, a negative "gap" will overlap all notes by that amount.

;

;INSTRUCTIONS

;

;First, set the FROM and THRU markers and highlight the track to be edited. Input the number

;of ticks you wish for notes to end relative to the start of the next note as well as the MIDI

;channel for the track. The program will remove or generate the overlap as requested. Make sure

;you have already adjusted the starting times of all of the notes or finished any quantizing to

;your satisfaction before running this program.

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

;

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

;As always, we begin this program with an opening "do" statement.

;This tells CAL that the program will be made up of multiple statements.

 0 (do

 ;Next we "include" the program "need31.cal" to be sure the user is running

;CAL version 3.1 or later. If not, the "need31.cal" program will end all CAL execution.

;The reason we need at least CAL 3.1 is because we are using an "included"

;program that must be able to pass data between itself and the calling program.

;CAL versions before 3.1 may not support this feature.

0 1 (include "need31.cal")

;Now we declare our variables.

;This variable determines the mode of operation. Default is to shorten only overlaps.

0 1 (int mode 0)

;This is the gap we will set between target notes. Default duration is 1 tick.

0 1 (int gap 1)

;Next we ask the user for the operating parameters.

;Ask user to select mode.

0 1 (getInt mode "Select Mode - 0 For Shorten Overlapping Only - 1 For Force All To Gap " 0 1)

;Ask user for gap/overlap amount.

0 1 (getInt gap "Ticks Of Gap (Pos Value) Or Overlap (Neg Value) Between Notes " -30 30)

;All of the work is done by the included "MONO.CAL" program. Now that we have set

;up all of the operating parameters for our run, we call MONO.CAL to do the job.

0 1 (include "mono.cal")

;Close the "do" that nests the program.

0 )

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

 


Program Text for MONO.CAL


;MONO.CAL version 1.0 - A Cakewalk CAL "Include" program for conditioning note overlaps

;Copyright 1998 by D. Glen Cardenas

;

;PROGRAM NOTES:

;This program is meant to be included in a larger program whenever a range needs to have any

;note overlap removed or preset to a specific amount. In that this is just an "engine" and not

;a complete program, it depends on some parameters being set by a parent program before

;this one is called. The "mode" of operation and the overlap "gap" is passed to MONO.CAL

;from the parent and there are some more variables declared here that are subsequently

;undefined at the end. There is no data passed from this program back to the parent. It just

;does its job and then returns to the caller.

;

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

;

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

;As always, we begin this program with an opening "do" statement.

;This tells CAL that the program will be made up of multiple statements.

0 (do

;Because this program depends on the order of the notes on the track being in the

;same order as they will voice, we include this program that tests for the condition

;I refer to as a track being split into "sub-tracks". This is a condition where after a

;group of edits on a track, Cakewalk is no longer storing all of the events as one

;continuous track but has sub-divided it. This is only a problem to CAL and is otherwise

;unnoticeable to the user. The condition will cause programs like this one to malfunction.

;If this condition exists, the included program will issue an error message and halt CAL.

0 1 (include "timerror.cal")

;Now we declare the variables we will be using for this program. At the end, we will

;"undef" or undefine them after we have finished using them so as not to clutter up

;memory for the calling program with variables it will not need. Because this program

;must have two notes in memory at the same time in order to do its job, we declare an

;array of variables to store note parameters in so we can have access to them during

;a subsequent pass of the loop. It is my personal convention to name the variables in

;an included program with the first letter capitalized. This is in concert with my personal

;convention of naming all variables in a parent program using all lower case letters. This

;way, it is impossible for me to accidentally declare a variable in both the parent and

;included program using the same name, thus avoiding any unexpected error messages.

;This will store the target note's pitch.

0 1 (int Pitch 0)

;This will hold the target note's MIDI channel.

0 1 (int Chan 0)

;This will store the target note's velocity.

0 1 (int Vel 0)

;The target note's duration goes in a "long" variable.

0 1 (long Dur 0)

;its event time uses a double word.

0 1 (dword Time 0)

;This will be a temporary storage location.

0 1 (int Temp 0)

;Now starts the loop that scans all events in the selected part of the sequence and does

;whatever needs to be done to them.

;For so long as there are events to be processed…

0 1 (forEachEvent

;and so long as the current event is a note…

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

;do the following.

0 1 2 3 (do

;We need to store a note in memory before we can do anything to it. In this program,

;I will demonstrate a quick and easy way of performing a note storage update. Here we test

;for "Temp" being other than zero. If it is, then we have already put a note in storage and

;we can proceed with the process of evaluating the gap between the current note and the

;note in storage. Later, we will place this current note into storage, or, if we are on the first

;pass of the program ("Temp" = 0) and have no note in storage, we bypass this entire section

;and jump strait down to the part where we store this current note. Either way, we will set

;"Temp" to 1 each time we store a note so to be sure to run the meat of the program on

;every subsequent pass.

;If this is not the first pass of the loop, we can process the stored note.

0 1 2 3 4 (if (> Temp 0)

;If the calling program has instructed this program to process only notes that overlap…

0 1 2 3 4 5 (if (== mode 0)

;There are two possible conditions that would allow us to proceed if "mode" is 0. First,

;if the user requested we remove any overlap ("gap" is positive) and the current note's

;start time is before the ending time of the note in storage (there is an overlap), then we

;will take action. The other condition is if the user asked for a set amount of overlap ("gap"

;is zero or negative) and the starting time of the current note is no more than "gap" number of

;ticks away from, or overlaps the ending time of the stored note, we likewise must take action.

;If either overlap is to be removed and there is overlap, or, overlap is requested and the

;notes are within range…

0 1 2 3 4 5 6 (if (|| (&& (> gap 0) (< Event.Time (+ Time Dur))) (&& (<= gap 0) (< (- Event.Time gap) (+ Time Dur))))

;do the following.

0 1 2 3 4 5 6 7 (do

;We set the gap or overlap by making the duration of the stored note equal to the difference

;between its starting time and the starting time of the current note minus "gap" if "gap" is

;positive, than there is a space between the ending of the stored note and the start of the

;current note. If "gap" is negative, then the ending time overlaps the starting time of the

;current note by "gap" number of ticks. If "gap" is zero, then the stored note ends and the

;current note begins on the same tick.

0 1 2 3 4 5 6 7 8 (= Dur (- (- Event.Time gap) Time))

;If the resulting modification to the stored note yields a note that still has a positive

;duration, then we insert the note into the sequence. As you will see in a moment, this

;step is necessary because when we placed this note into storage, we deleted it.

;If the duration of the stored note is still above zero, insert the note.

0 1 2 3 4 5 6 7 8 (if (> Dur 0) (insert Time Chan NOTE Pitch Vel Dur))

;Close the "do" nesting the modification process.

0 1 2 3 4 5 6 7 )

;If the note in storage failed to meet the conditions of the "if" statement that tested to see

;if overlap needed adjusting, then we perform the following ELSE statement. Here we

;reinsert the note back into the sequence unchanged.

0 1 2 3 4 5 6 7 (insert Time Chan NOTE Pitch Vel Dur)

;Close the "if" testing the notes for overlap.

0 1 2 3 4 5 6 )

;This is the ELSE part of the "if" that tested for "mode" equal 0. At this point, "mode"

;must equal 1 and so we will be setting all notes to the overlap conditions requested by

;the calling program.

0 1 2 3 4 5 6 (do

;We set the gap or overlap by making the duration of the stored note equal to the difference

;between its starting time and the starting time of the current note minus "gap". If "gap" is

;positive, than there is a space between the ending of the stored note and the start of the

;current note. If "gap" is negative, then the ending time overlaps the starting time of the

;current note by "gap" number of ticks. If "gap" is zero, then the stored note ends and the

;current note begins on the same tick.

0 1 2 3 4 5 6 7 (= Dur (- (- Event.Time gap) Time))

;If the duration of the stored note is still above zero, insert the note.

0 1 2 3 4 5 6 7 (if (> Dur 0) (insert Time Chan NOTE Pitch Vel Dur))

;Close the "do" nesting the code for "mode" being 1.

0 1 2 3 4 5 6 )

;Close the "if" testing for the value of "mode".

0 1 2 3 4 5 )

;Close the "if" testing for "Temp" equal 0.

0 1 2 3 4 )

;Regardless of whether this is the first pass or a subsequent pass, we will need to store

;the current note and delete it.

;Store the current note's pitch.

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

;Store the current note's start time.

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

;Store the current note's velocity.

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

;Store the current note's duration.

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

;Store the current note's MIDI channel.

0 1 2 3 4 (= Chan Event.Chan)

;Delete the note.

0 1 2 3 4 (delete)

;Make sure "Temp" equals 1 to show that a note is in storage.

0 1 2 3 4 (= Temp 1)

;Close the "do" nesting the code for the loop.

0 1 2 3 )

;Close the "if" testing for events being notes.

0 1 2 )

;Run the "forEachEvent" loop until all events are scanned, then close the loop.

0 1 )

;If a note was stranded in storage after the loop ended, insert it back into the sequence.

0 1 (if (> Temp 0) (insert Time Chan NOTE Pitch Vel Dur))

;Clear "Temp" from memory.

0 1 (undef Temp)

;Clear all note storage variables from memory.

0 1 (undef Time)

0 1 (undef Dur)

0 1 (undef Vel)

0 1 (undef Chan)

0 1 (undef Pitch)

;Close the "do" nesting this program and return to the calling program.

0 )

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


Demonstrations of Running MONOPHON.CAL


The following screen shots were taken from the Piano Roll view in Cakewalk. The first one shows a test pattern of notes that are arranged with decreasing amounts of overlap and increasing amounts of gap. As you can see, all of the notes are of the same duration.

 

This is the result of running MONOPHON.CAL using Mode 0 (effect only overlapping notes) with a Gap set for positive 30 ticks. The only notes effected are the first two, because only they overlapped. Note three starts on the same tick as note two ends on, thus technically there is no overlap and it was left unaltered. The first two, however, were shortened in duration so that they both end 30 ticks before the start of the note they used to overlap.

 

This shot shows what happens when using the same Mode of 0 but entering negative 30 ticks of Gap. This sets the program to effect notes that overlap or are within 30 ticks of each other and force them to overlap by 30 ticks. Now the first four notes are altered, but the fifth note, being more than 30 ticks away from the forth, was untouched. This setting would give a nice blend to a solo string or bass track.

 

Now we set Mode for 1, thus effecting all notes regardless of distance from their neighbor notes. Again, the Gap was set for positive 30 and now all notes have been altered to end 30 ticks from the start of the next note.

 

This shows the effect of Mode 1 operation using a Gap of negative 30. All notes are now overlapping their neighbors by 30 ticks.

 

These final two shots demonstrate the use of a Gap setting of zero. The first is Mode 0 operation, so only the first two notes are changed to meet the next notes nose to nose (remember, note three was already this close to note four, so nothing needed to be changed to make these notes meet). This setting would be good for a solo horn for example.

 

The other side of the same coin uses a Mode of 1 and Gap of 0 to bring all notes end to end.