compo 2.1 - Reference
© 2001 Bruno Lartillot
![]() |
![]() |
When launching compo, one gets a console window with a prompt (a character like ? or >) followed by a cursor echoing any text character typed on the keyboard. All the syntaxes described below can be entered at this level. The expression is evaluated by compo as soon as the enter key is pressed, with the expected side effects produced (mostly the creation of a midi or a postscript file). This is a simple way of trying compo or testing simple compo expressions. But this working mode has two limitations : It does not allow the entering of large expressions and it allows not to backup the typing for future sessions.
The normal way of working is to create one text file for each work using any text editor and to load such a file in the compo environment by typing :
(load filename)
The filename format depends on your system. It can be something like "/usr/dir/file" (Unix), "Principal:folder:file" (Mac OS) or "C:\dir\file" (Windows). Also, depending on your Lisp environment, the load function can figure as an entry in a menu with a dialog box allowing to select interactively the file to be loaded.
Since we are inside a complete Lisp environment, compo is only one set of packages among several others. In order to have the syntax described below working, we have to make the :compo-cm package the current one. This is done by making each text file to be loaded starting by :
(in-package :compo-cm)
Inside a text file, line separators or spaces can be inserted anywhere in the text, provided that they do not figure inside a name, a keyword or a number. This allows to structure and indent the text in a readable way. For example :
It is possible to put some comments inside a big work. A comment is any text starting with a semi-colon, until the end of the current line. For example :
Note that the comments are visible only in the source file, for a better comprehension
of the source, but have absolutely no effect in midi playing nor score printing.
![]() |
![]() |
note constructor |
The term note is taken in compo in a more general meaning than in the current language. Music can generally be described in terms of elementary notes, or atoms, and hierarchical structures combining such atoms. In compo, both atoms and structures are called notes since, as described below, they share a same set of properties. Hence the general and recursive definition is :
A note is either an atom (elementary note) or any structure of notes (container note).
The general form of the note constructor is :
(type [name] property* subspec*) (the brackets indicates that the name is optional, and the star indicates that a property or a subnote can appear any number of times, including zero).
The type is one of the symbols note, cnote, fermata, repeat-end, fermata-repeat-end or lyrics. cnote has the particularity to be constant relatively to the realization process (see realize). A fermata, a repeat-end or a fermata-repeat-end are just specialized notes characterizing the presence of a fermata a repeat end or both at the position where the note figures.
A name is any string (like "intro"). Except inside a lyrics section, it has no particular semantic effect.
A subspec can be a subnote, a repetitive subnote, a declaration or a reference as described below :
A subnote has the same form as the note constructor except that the type is not required. If it is not provide, it defaults to the same type as the root note.
An expression of the form n * subnote which means that the specified subnote is repeted n times.For example : 2*((:c)(:d)(:e)(:c)).
An expression of the form name = subnote which simply binds the specified name to the defined subnote. A valid name should not be a keyword (starting with :), should contain at least one letter and should not contain any occurence of the = and * characters.
A reference, in the form of a lexically defined name, that is, a name already defined globally (see setf) or inside the scope (a pair of parenthesis) where the reference appear.
For example :
(note a=((:c)(:d)(:e)) a)
(note a=((:c)(:d)(:e)) (:f a))
are valid expressions but :
(note (a=((:c)(:d)(:e))) a)
(note (a=(:c a)))
are not, since the definition and the reference of a are not in the same scope in the first case, and a is not completely defined when its reference appears in the second.
A name can be locally redefined, so in the expression :
(note a=((:c)(:d)(:e)) (a=((:f)(:g)(:a)) a) a)
the first reference of a refers to (:f)(:g)(:a) while the second refers to (:c)(:d)(:e)
The effect of encountering a reference is to have it replaced by the current value associated to the name. A declared name should be referenced at least once to have any effect in the realization.
A property is normally a couple of a keyword and a value. The keyword is the name of one of the property described below, preceded by a colon. For example :HEI is a valid keyword for a property. The value is one of the value allowed below in the description corresponding to a given property. For example :HEI 400 is a valid property.
Wherever a keyword is allowed as a value to a property, it can figure alone (i.e. not preceded by its property keyword). For example :G is a valid property and is an abreviation to :HEI :G (in english syntax).
Notes hold five properties which are said to be numeric properties : respectively the height, the duration, the position, the voice and the dynamics. In elementary notes, the numeric properties serve to compute final values (midi or score parameters). In a container note, the numeric properties serve as modifiers for the numeric properties in its subnotes.
This modification process of the compo-notes by their container is recursive. The root note modifies its subnotes, which then modify their own subnotes and so on. Note that the note objects are not actually modified. The scope of this modification is only the computing of the resulting sequence (this process is called the realization in the terminology of compo).
How the numeric properties are modified is explained below for each numeric property. As a convention, a term super-property-name refers to the value of the property property-name in the direct container of an elementary note.
![]() |
numeric property |
Represents the pitch in cents of an elementary note, C4 being equal to 0. In addition to numbers, keywords are allowed as heights. The format is :
For instance : 400, :G, :E5, :LA, :SOL#3 or :FFF2 are valid values for HEI.
nil is allowed as a value to hei. This is the way to define a rest note. :rest is a shortcut to :hei nil
Changing the syntax is done by typing the lisp expression :
(setf *default-syntax* :english)
or :
(setf *default-syntax* :latin)
If a keyword is provided, it is converted in cents according to a non-equal temperament as a default. If you want to switch in equal temperament, just enter the Lisp expression :
(setf *default-temperament* *equal-temperament*)
The reason why compo uses a specific temperament rather than work in equal temperament, is that it allows to maintain the difference between d sharp and e flat for example, which is often usefull, particularily when dealing with the classical tonal music. Currently, there are three possible values for *default-temperament* which are : *compo-temperament* (the default), *equal-temperament* or *pythagorician-temperament*.
HEI is modified to the value of the expression : (+ HEI SUPER-HEI)
HEI defaults to :C4.
![]() |
numeric property |
A proportional representation of the duration, where 1 corresponds to the quarter, 1/2 to the 8th, 4 to the whole note... In addition to numbers, the keywords :DW (double whole) :W (whole), :H (half), :Q (quarter), :8th, :16th, :32th, :64th (english syntax), :DR (double ronde), :R (ronde), :B (blanche), :N (noire), :C (croche), :DC (double-croche), :TC (triple croche) and :QC (quadruple croche) (latin syntax), eventually with one or two leading dots are allowed as durations.
DUR is modified to the value of the expression : (* DUR SUPER-DUR)
The final conversion of a duration to a midi duration is obtained by 1) multiplying the value by 1000; 2) rounding the result to an integer. Thus, any number corresponds to a valid midi duration (expressed in milliseconds).
DUR defaults to :Q.
![]() |
numeric property |
POS-MOD can be set to one of the three keywords :juxt (juxtaposition), :super (superposition) or :abs (absolute).
When set to :juxt, the horizontal position of the note is computed so the note is shifted, according to the value of POS, relatively to the end of the note appearing before it, when realizing a subnote list;
When set to :super, the horizontal position of the note is computed so the note is shifted, according to the value of POS, relatively to the position of its container (its super-note);
When set to :abs, the value of POS represents an absolute position for the note.
Here is an example of the three positionning mode :
(note
:pos 1 (:juxt :pos 0 :c4)(:juxt :pos 0 :e4))
(note
:pos 1 (:juxt :pos 0 :c4)(:super :pos 0 :e4))
(note
:pos 1 (:juxt :pos 0 :c4)(:abs :pos 0 :e4))
POS is modified to the value of the expression : (* POS SUPER-DUR)
The final conversion of a position to a midi offset is obtained by 1) multiplying the value by 1000; 2) rounding the result to an integer. Thus, any number corresponds to a valid midi offset (expressed in milliseconds).
POS defaults to 0 and POS-MOD defaults to :juxt.
:chord given as a keyword parameter to a note makes all the elementary subnotes defined inside its scope, at any arbitrary depth, to be superposed. So, for example (:chord (:c)(:c5 (:e)(:g))) is a shortcut to ((:super :c)(:super :c5 (:super :e)(:super g))).
![]() |
numeric property |
An integer representing the voice. In addition to integers, the keywords :french-violin, :treble, :soprano, :mezzo-soprano, :alto, :tenor, :baritone, :bass, :double-bass and :percussion are allowed as voices. This order determines the order of apparition of the voices, from top to bottom, in the result score. The keywords correspond to the descending integer from 1 to -8.
There is 16 voice classes which are the integer between 1 and -14. For a given class, all the voices which value is a multiple of 16 appart from this class is considered to belong to that class. For example, 16 belongs to the class 0 (or :treble), while 18 belongs to the class -14 (18 - -14 = 32 which is a multiple of 16), and -15 belongs to the class 1 (or :french-violin). See the :tied-voices keyword parameter of the score function for a concrete use of voice classes.
VOI is modified to the value of the expression : (+ VOI SUPER-VOI)
The final conversion of a VOI value to a midi chanel is obtained by : 1) taking the opposite of the value; 2) applying a modulo 16 to the result; 3) adding 1 to the result.Thus, any integer corresponds to a valid midi chanel number (from 1 to 16), and :treble to :percussion fill the range between 1 and 9, while :french-violin takes the chanel 16.
VOI defaults to :TREBLE.
![]() |
numeric property |
A number representing the dynamics as a ratio.
DYN is modified to the value of the expression : (* DYN SUPER-DYN)
The final conversion of a DYN value to a midi velocity is obtained by : 1) multiplying the value by 100; 2) rounding the result to an integer; 2) choosing the max between the result and 127; 3) Choosing the min between the result and 0.Thus, any number corresponds to a valid midi velocity (from 0 to 127).
DYN defaults to 1.
![]() |
coercers |
Five additional properties are defined, one for each numeric property. These "type" properties, when not null, act on the elementary notes constituting the result of the realization on two different ways : as filters or as coercers.
A type property act as a filter if its value is any Lisp type specifier. Each elementary note which numeric property value corresponding to the considered type property, doesn't match that type specifier is eliminated from the result. For example, if the value of coerce-pos is the form (rational 0 400), each elementary note which position is not included between 0 and 400 is eliminated from the result.
A type property act as a coercer if its value is a coercion specifier, that is, an expression of the form '(fun par1 par2 ... parn), where fun is a lisp function which takes a value and n optional parameters as arguments (n greater or equal to 0), and which returns a value of the same type. In that case, for each elementary note, fun is applied to the numeric property value corresponding to the considered type property, with the list (par1 par2 ... parn) as a remaining argument list, and the result replaces the property value.
Many coercion specifier functions are to be provided later on. Currently, the function mode can be used with heights. It enforces heights according to those of a given mode. For example, '(mode :C :D :E :F :GS :A :B) can be used to coerce a melodic cell to an harmonic minor mode (provided that it is in A key).
Keywords are allowed as parameters in a coercion specifier, depending on the concerned numeric property. As one can see in the example above, valid keywords for HEI are allowed as parameter in a coercion specifier given as a value to COERCE-HEI. It is the same for COERCE-DUR and COERCE-VOI, according to the keywords allowed in DUR and VOI.
![]() |
note constructor |
A note defined with the lyrics type denotes a section where elementary notes define lyrics to be written under the staves rather than real notes to be drawned on the staves. The text to be written is the string given as a name to each elementary note. This text will figure under the staff corresponding to the same voice than the voice of the elementary note defining it, and at the position of this elementary note, provided that there is at list one real note at this voice starting at the same position. For example, in :
"Oooh" is written under the D at the soprano voice, but "Aaah" is ignored since it figures at a position where no real note is present at the soprano voice, while "Iiih" is ignored since there is no real note at the tenor voice.
It is possible to superpose two stanzas (but not more) under a same staff by defining two lyrics sections. Man should take care in that case to identify the lower stanza by giving the corresponding lyrics section a heighth lower than c4 (the default) :
Accordingly to compo's style, it is possible to have the same lyrics written under several staves while writting it just once :
![]() |
global note construction declaration |
declare-note (name form)
Names globally defined that way can be present as references in any subsequent note constuction. For instance :
(declare-note *a* (note (:c)(:d)(:e)))
(declare-note *b* (note (:d)(:e)(:f)))
(note *a* *b* *a*)
is a valid note construction referencing two globally named subconstructions (though it is not required, it a standard Common Lisp idiom to enclose a global name between stars).
compo has a unique name space. So a global name can be shadowed by a local one, as in the following :
(declare-note a (note (:c)(:d)(:e)))
(note :bf (a=((:f)(:g)(:a)) a) a)
The first reference of a corresponds to the local declaration f g a while the second one corresponds to the global c d e.
Names declared using declare-note, unlike those declared using setf, cannot be given as an argument to midi, score, realize or nrealize.
![]() |
global variable assignment |
setf (name note)
This standard Common Lisp facility of global variable assignment is used in compo to allow global declarations, like declare-note. Names globally defined that way can be given as an argument to score, midi, realize or nrealize.
(setf *a* (note (:c)(:d)(:e)))
(midi *a*)
(score *a*)
Names declared using setf, unlike those declared using declare-note, cannot be referenced inside subsequents note constructions.
![]() |
function |
realize (note &key :context :depth)
Returns a new note equal to the realization of note. realize works on a full copy of note which remains unchanged.
A context determines the behaviour of the realization applied to a note. The key argument :context defaults to the standard context, that is, the unique behaviour available basically in compo, which is described in the frame of this document. This ability to change the context allow to define in the future new realization behaviours.
The standard context causes the realization to return a cnote (a constant note), containing at most as many elementary notes as the note argument, each transformed and coerced according to the mechanisms described above, along with the descriptions of the note properties.
For example :
(realize (note :d4 :coerce-voi '(eql :bass) (:treble :super (:c)(:d)(:e)) (:bass :super (:c3)(:g2)(:c3))))
In that example, the coercer on voices specified that only the note which voice is :bass are kept in the result. The global height :d4 transposes a tone upwards the height of the three kept elementary notes which become respectively d3, a2, and d3, while their positions are normalized in absolute values. As a synthetic information, the position and duration of the root is recomputed in order to reflect the global position and duration of the whole structure (i.e. it starts at position 0 and has a duration of 3 quarters normalized to a dotted half). Finally, the root is changed to a cnote so reapplying again the realization on it will not change the result. The constant property of a cnote can be mathematically defined as the following :
(realize (anynote)) <=> (realize (realize (anynote)))
We suggest you to try this example with incremental changes to get familiarized with the way the realization works.
The key argument :depth allows to stop the realization process at a specified hierarchic level. In that case, each note encountered at this level will be considered as being elementary, even if it does contain subnotes. :depth defaults to nil, i.e. the realization is performed completely.
For example :
(realize (note (:c (:c)(:e)(:g)) (:g3 (:c)(:g)(:c5)) (:c (:c)(:e)(:g))) :depth 1)
Realizing this note at the depth 1 causes the result to keep only the three intermediary notes, forgetting the nine elementary notes.
![]() |
function |
nrealize (note &key :context :depth)
nrealize is the destructive version of realize. It saves the cost of cloning its note argument, in the cases where there is no need to keep it unchanged.
![]() |
function |
midi (note &key :midi-file :ptf :tempo)
Generates a midi structure corresponding to the realization of the note argument. midi takes in charge the realization so, according to the constancy of realize, (midi (anynote)) is an equivalent abreviation of (midi (realize (anynote))). The second form is necessary only when particular realizations are expected (destructive, at a specified depth, with alternative contexts...).
The :midi-file keyword argument allows to change the emplacement and name of the produced midi file. It defaults to the file pointed by the value of the variable *default-midi-file*. It accepts any Lisp pathname.
The :ptf keyword argument determines how the heights in cents are converted to frequencies by midi. It defaults to the function ptf-eq-440, which converts cents in a frequency scale in equal temperament and according to the pitch fork A=440hz. Other conversion function can be given. In addition to ptf-eq-440, compo provides ptf-py-440 which concerts cents in a frequency scale in pythagorician temperament and according to the pitch fork A=440hz.
The :tempo keyword argument defines the number of quarters per minute. It defaults to 60.
![]() |
function |
program-change (voi &optional program)
Change the midi program (musical instrument) associated to the specified voice.
voi accept any integer or keyword allowed as a value to the voi property of a note.
program accept any of the following keywords :
:acoustic-grand-piano :bright-acoustic-piano :electric-grand-piano :honky-tonk :electric-piano-1 :electric-piano-2 :harpsichord :clavicord
:celesta :glockenspiel :music-box :vibraphone :marimba :xylophone :tubular-bells :dulcimmer
:drawbar-organ :percussive-organ :rock-organ :church-organ :reel-organ :accordian :harmonica :tango-accordian
:nylon-acoustic-guitar :steel-acoustic-guitar :jazz-electric-guitar :clean-electric-guitar :muted-electric-guitar :overdriven-guitar :distortion-guitar :guitar-harmonics
:acoustic-bass :finger-electric-bass :pick-electric-bass :fretless-bass :slap-bass-1 :slap-bass-2 :synth-bass-1 :synth-bass-2
:violin :viola :cello :contrabass :tremolo-strings :pizzicato-strings :orchestral-strings :timpani
:string-ensemble-1 :string-ensemble-2 :synth-strings-1 :synth-strings-2 :choir-aahs :voice-oohs :synth-voice :orchestra-hit
:trumpet :trombone :tuba :muted-trompet :french-horn :brass-section :synth-brass-1 :synth-brass-2
:soprano-sax :alto-sax :tenor-sax :baritone-sax :oboe :english-horn :bassoon :clarinet
:piccolo :flute :recorder :pan-flute :blown-bottle :shakuhachi :whistle :ocarina
:square-lead :sawtooth-lead :calliope-lead :chiff-lead :charang-lead :voice-lead :fifths-lead :bass+lead
:new-age-pad :warm-pad :polysynth-pad :choir-pad :bowed-pad :metallic-pad :halo-pad :sweep-pad
:rain :soundtrack :crystal :atmosphere :brightness :goblins :echoes :sci-fi
:sitar :banjo :shamisen :koto :kalimba :bagpipe :fiddle :shanai
:tingle-bell :agogo :steel-drums :woodblock :taiko-drum :melodic-tom :synth-drum :reverse-cymbal
:guitar-fret-noise :breath-noise :seashore :bird-tweet :telephone-ring :helicopter :applause :gunshot
If program is not provided, the user is prompted to choose interactively a value from this list.
![]() |
function |
init-programs ()
Reinits for all the midi chanels the midi program to 0 (acoustic grand piano).
![]() |
function |
test-programs ()
Sends one midi note to each midi chanel in order to actually activate program changes.
![]() |
function |
Generates a score corresponding to the realization of the note argument. Like midi, score takes in charge the realization so, according to the constancy of realize, (score (anynote)) is an equivalent abreviation of (score (realize (anynote))). The second form is necessary only when particular realizations are wished (destructive, at a specified depth, with alternative contexts...).
The :mode keyword argument determines the global mode, either :major-mode or :minor-mode, of the work. It is used to determine the key signature of the score. It defaults to the current value of the variable *default-mode*.
The :key keyword argument determines the global key of the work. It is used to determine the key signature of the score. It accepts any value among the keywords :C :D :E :F :G :A :B :CS :DS :FS :GS :AS :CF :DF :EF :GF :AF and :BF. It defaults to the current-value of the variable *default-key*.
The :time keyword argument determines the global time signature of the score. It can be :
The time defaults to the current value of the special variable *default-time*.
The :output-type keyword argument determines the type of formatting. It can be :postscript or :quickdraw. In :postscript, the score is formatted as a postscript file. In :quickdraw (only on the Macintosh), the score is immediately displayed in new windows in the Lisp environment. It defaults to the current value of the variable cmn::*cmn-output-type*.
The :output-file keyword argument allows to change the emplacement and name of the produced postscript file. It defaults to the file pointed by the value of the variable *default-output-file*. It accepts any Lisp pathname.
The :size keyword argument fixes the proportional size of the score. It defaults to the current value of the variable *default-size*.
The :free-expansion-factor keyword argument fixes the horizontal proportional spacing of the glyphs. It defaults to the current value of the variable *default-free-expansion-factor*.
The :staff-separation keyword argument fixes the vertical spacing between staves. It defaults to the current value of the variable *default-staff-separation*.
The :line-separation keyword argument fixes the vertical spacing between lines of score. It defaults to the current value of the variable *default-line-separation*.
The :initial-onset keyword argument determines the position at which starts the score. Along with the :pos parameter of the note constructor, It allows to have a score started with an incomplete measure. For example : (score (note :pos 3 (:c)(:d)(:e)) :initial-onset 3) will print a score starting with an only one time first measure. :initial-onset defaults to 0.
The :tied-voices keyword argument allows to specify the voice class list for which voices should be tied. That is, all parts belonging to a same voice class which is tied are represented on the same staff. For example :
As a default, no voice class is tied.
![]() |
function |
cleave-change (voi &optional cleave)
Change the cleave associated to the specified voice class.
cleave accept any of the following symbols :
cmn::french-violin cmn::treble cmn::tenor-treble cmn::soprano cmn::mezzo-soprano cmn::alto cmn::tenor cmn::baritone cmn::baritone-C cmn::baritone-F cmn::bass cmn::sub-bass cmn::double-bass cmn::percussion
If cleave is not provided, the user is prompted to choose interactively a value from this list.
![]() |
function |
init-cleaves ()
Reinits for all the voice classes the cleave to its default value.
![]() |
function |
This function describes the full grammar of the note constructor.
![]() |
function |
This function reminds all the keywords allowed as a midi program to the function program-change.
![]() |
function |
This function reminds all the keywords allowed as a cleave to the function cleave-change.
![]() |
At the root of the compo package, the file localization.lisp contains the list of all the textual messages used by the system. Feel free to change those texts (only the texts, not the variable names) in order to have those texts localized to your natural language. Then just restart compo. At this time, your modification will be taken into account by the system.