This chapter attempts to define the Rubato language with respect to the major syntactic and semantic components of the language.
A EBNF grammar[21] for the language is presented in Appendix A. The grammar gives a reasonably precise syntactic definition of the language, although it is deliberately less precise and much more forgiving in the semantics of the language. If the grammar is used to implement a compiler for the language, the semantic analysis phase should weed out questionable constructs which are syntactically acceptable to the grammar.
This chapter will not attempt to describe the semantics of the language in a formal or rigorous manner, i.e. using the techniques of denotational semantics. An interesting follow-up to this thesis would be a rigorous denotational semantic specification of the Rubato language coupled with axiomatic and fixed point analysis of the semantics of programs written in the language.
The Rubato language has the following fundamental types or objects:
A number is a whole (signed integer) value ranging from MININT to MAXINT(12). A number is not a true object in the language but more like a fundamental type. It cannot exist independently but must always be bound to an object. It is used as a component of the various other types in the language and is the type which is the result of an arithmetical expression within the language.
Notes are the most basic building blocks of the language. A note corresponds to a musical note in a written score. In the Rubato language, a note is composed from its individual attributes. If the note is regarded as an event specification in a multidimensional perception space, then its attributes are the principal dimensions of the space.
The attributes of a note are:
Each note in a program written in the language will have all the above attributes, even if they are not specified explicitly by the user. Note attributes which are not specified will default to a value which depends on the lexical context of the note in the program.
A phrase is a collection of block objects(14) which will be invoked sequentially. A phrase can contain object definitions which must occur in the beginning of the phrase, before the actual objects contained in the phrase.
A chord is a collection of block objects which will be invoked simultaneously or concurrently. A chord can also contain object definitions which must occur in the beginning of the chord, before the actual objects contained in the chord.
An envelope is a specification of a sequence of linear functions to be applied in sequence to objects within a phrase. The specification of envelopes have not been finalised and envelopes are not currently implemented within the system.
A variable is an object that may hold a number. It is simply a read and write storage location which initially contains an undefined number. Once written to, future reads from the storage location will yield a number equivalent to the last number stored in the location. Variables may be used in the language wherever a number may be used.
A procedure is either a phrase or chord which accept parameters(15) when invoked and pass these parameters onto the phrase or chord.
A function is a procedure that returns a number back to the environment of the invoker. Functions also accept parameters when invoked and pass on these parameters to the phrase or chord. The number returned by a function can be used in the language wherever a number may be used.
A template is a procedure with note parameters. It has an implicit phrase associated with it which is simply the phrase consisting of each note specified in the parameter in sequence from the first parameter of the template to the last parameter of the template.
An arithmetical expression is a group of numbers joined by either unary or binary arithmetical expression operators forming an expression tree when parsed. An expression has a result value which is equivalent to the number returned when the expression is evaluated. An expression may substitute for a number whenever one is required.
A statement is an object that may be contained within a phrase or a chord other than a chord or a phrase. There are many types of statements, including default statements, control flow statements, assignment statements, call statements which invoke a procedure or a template and other statements relating to music performance such as the tempo statement.
Programs written in the language are composed of tokens. Blanks, tabs, newlines, and comments (collectively, "white space") are ignored except as they serve to separate tokens. Some white space is required to separate otherwise adjacent identifiers, keywords and constants. If the input stream has been parsed into tokens up to a given charater, the next token is taken to include the longest string of characters which could possibly constitute a token. Upper case and lower case characters are considered significant when parsing a token.
The character sequences /* and */ introduce and terminate a
comment, respectively. Comments do not nest.
The following classes of tokens are distinguished in the language:
An identifier is a sequence of letters and digits. The first character must be a letter. An identifier corresponds to a storage location either containing an object, i.e. a variable, or containing a pointer to an object, i.e. everything else. All characters in an identifier are considered significant.
The following identifiers are reserved for use as keywords, and may not be used otherwise:
and channel chord conductor default delay do duration else envelope extern function identifier if key major measure minor mod not note or patch phrase pitch pressure procedure repeat return scale template tempo then to var velocity while
Some reserved words are not currently implemented, but they are still recognized by the parser.
Constants are tokens returning a number. This can either be a sequence
of digits 0 to 9, or an arithmetical expression of constant
values which evaluate to a number.
Pitch values are special constants returning a
key.
A key is a letter from the set of
{A,B,C,D,E,F,G,a,b,c,d,e,f,g,r,R}
together with a sequence of
accidentals
bound to the key. An accidental is one of
#
$
%
Some special characters are accepted by the language in the same way as keywords are. These characters are given the following symbolic names.
{
}
[
]
(
)
=
|
&
\
;
!
@
'
'
_
^
.
:
,
==
<>
<
>
<=
>=
+
-
*
/
~
These are usually written in infix notation, with precedence of operators enforced by a superset of algebraic precedence rules. The following binary operators are accepted in order of increasing precedence:
or
and
== and <>
<= and >=
< and >
+ and -
* and / and mod
In addition, the following unary operators are available, in order of increasing precedence:
not
~
. and :
The following may also be treated like a number in an arithmetical expression, these are collectively termed number equivalents.
key followed by a pitch constant. It is
numerically equal to the pitch number corresponding to the pitch constant.
!! gives the current
(default) value of the duration attribute.
function_identifier(parameter_list)
A parameter list is a list of numbers, notes, phrases, chords, templates, procedures, functions or envelopes separated by COMMA.
A Note can be:
pitch or the symbol & followed by an
arithmetical expression.
An arbitrary number of attributes may be tacked on to the end of a note specification and forms part of the note. Attributes may be tacked on in any order and will be evaluated from left to right. In other words, if two attributes tacked on the note have the same type, the effect is either cumulative or the rightmost attribute will have precedence over the leftmost attribute.
Attributes are specified as either an attribute operator followed by an expression, an attribute operator with a relative expression, or an attribute operator with no trailing expressions. The following attributes are currently recognized:
delay or \
. or :
duration or !
^ or _
velocity or ;
pressure or @
patch
channel
A relative expression is one of the following operators:
+
-
*
/
mod
followed by an expression. An attribute specified with a relative expression can always be transformed semantically to an attribute specified with an expression by the following rule.
attribute rel_operator expression
can be transformed to
attribute current_value rel_operator ( expression )
For example, ;+20*3 is semantically equivalent to
;(;;+(20*3)).
DELAYMUL and DELAYDIV attribute specifications are special cases. A
factor
is a number or a number equivalent which is not specified as an
arithmetical expression. A number equivalent is a key factor, an identifier
bound to a number, the current value of an attribute, or the value returned
by a function call. However, an arithmetical expression enclosed
within parentheses is regarded as a factor. A DELAYMUL and DELAYDIV
attribute specification can be semantically to a delay attribute
specification.
.factor
is transformed to
delay( current_value * factor )
and
:factor rel_operator expression
is transformed to
delay( ( current_value / factor ) rel_operator expression )
The following types of statements are currently valid within a phrase or chord body. Notice that within this section, a statement is a term referring to either a statement, note, phrase, chord, or envelope.
ifexpressionthenstatement1
ifexpressionthenstatement1elsestatement2
Evaluate expression. If result is nonzero, execute statement1, else execute statement2, if specified.
repeatexpressiondostatement
Evaluate expression. If result is nonzero, execute statement iteratively the number of times specified by expression.
whileexpressiondostatement
Evaluate expression. If result is nonzero, execute statement and reevaluate expression repeatedly until expression evaluates to zero.
dostatementwhileexpression
Execute statement and evaluate expression repeatedly until expression evaluates to zero.
tempoexpression1=expression2
expression1 must evaluate to the number of units used as a counting quantity. expression2 evaluates to the number of times the counting quantity must occur in one minute.
defaultpitch1=pitch2
defaultattribute
Changes the default value associated with note pitches or attributes. The first form of the statement implies a transposition of all notes up or down given by the offset of pitch2 relative to pitch1. The second form sets the default value of the note attribute to the value specified.
identifier=object
Assigns (binds) object to identifier. Identifier assignments are 'strongly-typed', i.e. an object of a given type can only be assignmed to an identifier of the same type.
identifier(parameter_list)
Invoke a template or procedure, depending on the type of the identifier. The syntax of a template or procedure call is deliberately made identical to the syntax of a function call.
returnexpression
Only valid within the context of a function body, this statement returns the value of the expression to the invoker of the function.
Identifiers may be defined within the body of a phrase or a chord and 'typed' to an object type. Definitions must occur before the executable body of the phrase or chord. The definition of an identifier may also bind the identifier to an object. If a binding is not effected in the definition, the identifier can be bound later on within the phrase or chord through an assignment statement.
The syntax of an identifier definition is:
type identifier
type identifier=object
procedureidentifier(parameter_defs)block
functionidentifier(parameter_defs)block
templateidentifier(template_defs)
A type is either a note, phrase, chord or envelope. parameter_defs is a list of definitions with no initial bindings separated by COMMA. A block is either a phrase or a chord. template_defs is a list of attribute lists separated by COMMA.
The Rubato language employs static or lexical scoping rules on nested definitions. Definitions may be nested in the same way phrases or chords may be nested. Lexical scoping implies that the scope of an identifier is delimited by the phrase or chord that encloses the definition. The identifier is considered undefined outside the phrase or chord. Hence, the deeper the nesting of a phrase or chord, the more identifiers are defined within it (as it can access all its local variables plus variables belonging to its lexical environment). Conversely, a phrase or chord is not allowed to access variables belonging to phrases and chords nested within it. If an identifier declared within a phrase or a chord possesses the same name as an identifier declared outside the phrase or chord, the locally defined identifier hides or suspends the definition of the previous definition until the end of the phrase or chord.
Definitions may also occur outside the body of a phrase or chord, within the main source file. Such identifiers have global scope for the rest of the source file.
External definitions are like global definitions, except that they are
preceeded by the keyword extern. Also, they may not be
initialized, i.e. bound to an object in the definition. External
definitions also have global scope for the rest of the source file, but the
actual definition of the (global) identifier lies within another source file.
References to the same external or global identifier name are references to
the same object.
conductorblock
Every complete Rubato program (merger of the individual source files linked
together) must have one conductor definition. It represents the
phrase or chord that is executed by the Rubato machine when initially
started up.
These 'statements' control the action of the compiler when parsing the source file and are not actually part of the program proper.
$keypitchmajor
$keypitchminor
$keyaccidental_list
This controls the key signature that notes coded in the file should be interpreted in. Currently, only major keys are implemented. The pitch specified may include accidentals.
The alternative $key specification takes an
accidental_list,
which is a list of pitches with accidentals separated by COMMA.
$defaultpitch1=pitch2
This controls the default octave or initial transposition of note pitches coded up for the rest of the language. It forces the compiler to transpose all notes coded up by an offset equal to the pitch number of pitch1 subtracted from the pitch number of pitch1. Uppercase pitches and lowercase pitches can be transposed independently, allowing two octaves to be readily accessible without the use of INCPIT or DECPIT operators.
A source file in the language is simply a list of global or external
definitions together with a conductor definition. Compiler control
lines may intersperse the definitions.
Next: Chapter 6: A SPECIFICATION OF THE RUBATO MACHINE
Previous: Chapter 5: A SPECIFICATION OF THE RUBATO LANGUAGE
Back to: Table of Contents