ftp.nice.ch/pub/next/unix/music/cm.sd.tar.gz#/stella/tutorial/stella.rtf

This is stella.rtf in view mode; [Download] [Up]

Composing in Stella

Heinrich Taube
Zentrum fuer Kunst und Medientechnologie
Ritterstr. 42    7500 Karlsruhe 1    Germany
Net: hkt@zkm.de, hkt@ccrma.stanford.edu
Vox: +49 721 9340 300
Fax: +49 721 9340 39

Contents

0. Introduction
1. Getting started
2. Loading, Browsing and Listening
3. Creating material using Copy and Paste
4. Simple structure and data modification
5. New, Go and Set
6. Saving material
7. Describing music algorithmically
8. Recording and importing material
9. Map
10. A complete example
11. Defining new objects
12. Manuscripting data using CMN
13. Describing musical layout

Lacunae and bugaboos
Appendices


0. Introduction 

0.1 About this tutorial

This tutorial provides a simple introduction to writing music in Stella.   The examples were generated using Franz Allegro Common Lisp, and might appear slightly differently in other Lisp implementations.

0.1.1 Font conventions

The general body of text is printed in Times Roman font.  Times Bold is used for section headings, and to highlight dictionary terms.  Times Italic is used in the main text to stress terminology, and in the examples to highlight meta comments about what you are observing as you perform the example.  The examples are printed using the Courier font family.  Courier Medium is used to display what is printed to the terminal. Courier Bold is used to display what should be typed.   Courier Bold Italic is used to show "invisible" input.  For example <cr> means hit the Return key.  Because input normally ends with a carriage return, the <cr> is shown only in the event of an empty line of input.

0.1.2 Using the examples

To use the tutorial, simply follow the directions and enter whatever is highlighted in Courier Bold or Courier Bold Italic.

Several of the examples refer to data files associated with this tutorial.  Make sure you know where the tutorial directory is located (it should be under Common Music's main source directory in the subdirectory stella/tutorial) and that the tutorial directory contains the following files: "opus1.stella", "noodle.midi" "section.midi" and "fm.ins".

0.1.3 Musical examples and output syntax

In order to remain as simple and as general as possible, this tutorial uses Common Music's Midi output syntax and the midi-note class of object for most of the examples.  However, the examples could actually be used in conjunction with any of Common Music's output syntaxes: Midi, Music Kit, Common Lisp Music (CLM), CSound, or Common Music Notation (CMN), with the caveat that object slot names might change, some commands operate differently from one syntax to another, and that some commands cannot support some syntaxes.  In particular, the Listen command may currently only be used in conjunction with Midi and CLM.  See Appendix B for a description of which commands support which syntaxes.

If you want to use Stella with CLM, section 11 of this tutorial shows how to define a new class of Stella note object for a CLM instrument, and how to define output methods to send data to score files or directly to sound files.

If you want to use Stella with either the Music Kit or Midi, the system comes with the full class structures predefined.

See Appendix A for a complete listing of the predefined object classes in Stella. 

0.2 What is Stella?

Stella is a system that supports both algorithmic and editorial styles of music composition in Common Music.  By algorithmic we mean the specification of musical events indirectly through a program that creates them.  By editorial we mean the specification of musical material directly by the composer.  We simply use the style most appropriate for a given situation.  Since it is also possible to algorithmically generate material that is then editable, we can even combine styles while composing a single section of music.

The system comes with an editor that supports a simple, non lisp command interface.  The editor is ugly but powerful, sort of an Arnold Schwarzenegger of music editors, but it has the advantage of being free and running on any computer that supports Common Music and CLOS.  Since an editor is provided, someone who doesn't know Lisp can use the system to write music, but don't kid yourself -- you should know some lisp and it is a requirement if you want to write algorithms.

0.3 Class overview

Before starting to work with Stella, we define some terms that will be used many times as we work through the examples.  For a full overview, see appendix A for a listing of all the "public" classes in the system.

0.3.1 Class definitions

Algorithm. A type of container that computes elements.
Container. A type of object that holds or creates other objects.
Element. A type of object that cannot contain other objects and is the root class for basic musical data.
Generator. A type of container that is an algorithm and a thread.  If a generator has no elements, it can create them because it is an algorithm.  Otherwise, it accesses its current element set as a thread.
Heap. A type of thread that, when called upon to produce its element, first shuffles them to produce a random ordering.
Merge. A type of container that implements parallel access to elements by processing their containers in a scheduling queue. 
Midi-note. A type of note that is capable of producing midi output in real time or to midi files.
Note. A type of element that represents musical output of some sort.
Rest. A type of element that represents silence.
Object. A unit of structure in Stella.  An object is also sometimes called an instance.
Slot. An attribute of an object capable of holding one or more values.
Thread. A type of container that implements sequential access to its objects.

0.3.2 Class structure

The following graph shows the relationship between the important classes discussed in this tutorial:

                 Object
               /      \
         Container       Element
        /   |   \       /   \
    Thread Algorithm Merge  Rest  Note
     /  \   /              /  \
  Heap Generator            Fm   Midi-Note


1. Getting started

1.1 Entering Stella (so to speak)

Boot a Common Lisp image that contains Common Music and Stella must be running and then use the (stella:stella) Lisp function to start up Stella.  If you are in Franz Common Lisp, you can also use the :stella Lisp command to invoke Stella.  So let's try our first example:

	<cl> (stella:stella)

	Type ? for help.

	Stella [Top-Level]: 

Congratulations, you are now in the main command loop of Stella's editor.

1.2 About the prompt

After Stella booted up, a brief help message was printed and Lisp's prompt changed from "<cl>" to "Stella [Top-Level]:".  The prompt changed because we left the main Lisp environment and entered Stella's editor (also called the "command interpreter" at various times in this document).  Stella's prompt consists of two parts, the name of the editor followed by the name of the current focus object, printed inside [].  In our case the focus object is called Top-Level, the name of the system container that holds all of the containers that the editor currently knows about.  Stella has a number of commands that allow us to move around in the structures we create.  Whenever we move to a new object, the [] portion of the prompt will be updated to show the new focus object. The focus object is important because it may serve as the "root" object for relative referencing, ie for referring to other objects relative to our current position.  Much more about this later.

1.3 Using the help facility

Now that we have entered Stella, the first thing to do is to follow directions and type ? for help:

	Stella [Top-Level]: ?
	
		?                  Show this help.
		ADD                Add objects to container.
		ARCHIVE            Archive objects to file.
		CHANGE             Change the class of objects.
		... rest of command listing omitted
	
		Stella [Top-Level]: help help

		Use the HELP command for detailed help about the commands 
		summarized above, or to see overviews about the following 
		general topics: REFERENCING INTERPRETER EXPRESSIONS.

	Stella [Top-Level]: 

Stella's on-line help facility shows either short or long help. To see the command summary and short help use the ? command.  Use the main Help command to see detailed help about a particular command or topic.  For example, to see the documentation about the Help command itself, type:

	Stella [Top-Level]: help help

	  HELP {topic}
  
	  Arguments:
  
	    {topic}               Command name or topic.
  
	  Overview:
  
	  The HELP command displays detailed help about {topic}, which
	  may be a command name or a general topic. For  a listing of
	  available commands and topics use the ? command.
	  
	  See also:
	  
	  ?

	Stella [Top-Level]: 

Stella's help texts all follow a similar format. The first line of help displays the command line syntax, the name of the command followed by its command arguments, if any.  This line corresponds to what you would type to invoke the command from Stella's main prompt.  Documentation about the arguments follows the syntax description.  Next comes the Overview section that contains the general help text.  Finally, a  See also field points you to related commands and topics.  Now try using the Help command on several commands of your choice, for example Show, List, Set or Map.

1.4 About the command interpreter

Before going any further, let's stop briefly to examine some of the properties of the command interpreter itself, so that we learn how to interact with Stella in the most effective manner before we start to actually work with musical material. 

1.4.1 Command invocation

The first thing to note is that command names are case insensitive, and that you only need to type enough of a command name to distinguish it from the other commands.  For example, our previous example:

	Stella [Top-Level]: help help

could be typed:

	Stella [Top-Level]: he he

because he is enough to uniquely identify the Help command in the full command set.   Notice also that,  if you are running Franz Common Lisp, you do not have to start a command name with a colon, as you would if you were at the main Common Lisp prompt.  If this is too confusing just continue to start command names with colons, so our previous example could also be written:

	Stella [Top-Level]: :he :he

1.4.2 Command line prompting

Commands in Stella normally need more information than just the name of the command itself.  It is usually convenient to type some or all of this information directly on the command line, in the form of command line arguments following the name of the command.  In our previous example, 

	Stella [Top-Level]: help help

the first help was the name of a command, and the second help was a command argument, in this case the name of the command we wanted to read help about.  But we could also have invoked the Help command without any arguments, in which case Stella would prompt us interactively for the missing information:

	Stella [Top-Level]: help
	Help topic: help
	
	... help text omitted.
	
The global variable *command-prompting* controls whether or not you want command line prompting enabled.  The default value of *command-prompting* is t, which means that if  you do not supply command arguments, or if the information you supply contains errors, you will be reprompted for the information as the command starts processing.  If you set *command-prompting* to nil, then all command information must appear on the command line.  Missing or illegal input then produces a syntax or error message and you are returned directly back to Stella's main prompt.  Set *command-prompting* to nil if you prefer a terse input style of interaction.

1.4.2 Command argument syntax

Since each command has its own notion of how many arguments it accepts and what type of information is legal, its difficult to give specific information about command line syntax at this point.  But as a general observation,  note that many commands follow the general form of: 
command {references} {arg2} {arg3} ... 
where command is the name of the command, {references} is the name or location of one or more objects in the system, and each {arg} thereafter is an additional command argument.  (the brackets {} mean that you type something in place of the bracketed argument, they are not included as part the command data ).  Almost all commands expect some sort of object reference as the first argument.  If {references} includes more than one reference, each reference in the sequence must be delimited by a comma.  When reading command documentation via Help, note that a plural argument, such as {objects} or {positions} or {ranges} means that the argument accepts one or more values, with values separated by comma if more than one. 

Object referencing itself can get quite complex and is covered in a later section of this tutorial.  There is also a full explanation in the on-line help topic: Referencing

1.4.3 Responding to command prompts

As mentioned in the previous subsection, a command will prompt for any additional information it needs after the command line arguments have been processed.  When a command prompts for additional information, there are normally several possible courses of action:

· Answer the prompt.

· Type ? for help.  Typing ? to a command prompt will often print some help about what the command is expecting as input.

· Type ^ to abort the command.  In almost all cases, typing ^ (the carat symbol) as input to a prompt will abort the command and return you to Stella's main command loop.

· Type <cr> to accept a default value, if one is offered.  It is often the case that a command will supply some default value for you to select simply by hitting the Return key in response to the prompt.  The default value is always displayed along with the prompt inside a parenthetical expression equating the Return key with the default value.  For example (<cr>=123) means that hitting the Return key is the same as typing 123.

Since ? is a legitimate command name, typing it to the Help command's topic prompt will print its command help texts, and not perform their usual prompting role.  This is the exception that proves the rule...

1.4.4 Typing Lisp to the command interpreter 

Stella's command interpreter is actually a full Lisp interpreter.  We generally don't need to type any lisp expressions to work with objects in the system, but there are times when it is appropriate, and it is nice to have the full power of Lisp handy.  To evaluate a Lisp expression, simply type the expression at the main command loop prompt:

	Stella [Top-Level]: (floor 1 2)

	0 
	1 

	Stella [Top-Level]: 

Note that general Lisp evaluation is supported in the main command loop processing only, and not while a  particular command prompts for additional information. 

Since it is legal to type not only command names but also Lisp variable names in the main command loop, the command interpreter must have some way to distinguish between mistyped command names and variables to evaluate.  The convention we follow is taken from the Symbolics Machine: to evaluate a line of input consisting of a variable name, precede the variable name by a comma:

Stella [Top-Level]: ,*standard-tempo*

60 

Stella [Top-Level]: 

(Note that the * character in a lisp variable name such as *standard-tempo* has nothing to do with using the token * as a wildcard reference in a command!  (In Common Lisp, a variable name beginning and ending with the * character means that it is a global variable that is safe for the user to reset.))

1.4.5 Continuing from error breaks

Since Stella allows general Lisp evaluation, there must be some way of recovering from errors without ending up back in the main Common Lisp environment.  To return to Stella's command interpreter from an error break use the :tl error break command. Use this command even if it isn't listed in the error break message. (:tl stands for Top Level, the name of Stella's interpreter).   Note that, until the error has been resolved, we are in the general Lisp debugger and not inside Stella, so the colon in  :tl  must be included. 

	Stella [Top-Level]: (first 1)
	Error: Attempt to take the car of 1 which is not a cons.

	Restart actions (select using :continue):
	 0: Return to Top-Level.
	 1: Exit Top-Level.
	[1] <cl> :tl

	Stella [Top-Level]: 

In Franz or AKCL, you can often type :continue 0 as well, but that is more typing than :tl.  In MCL 2.0, either type Command-. or select "Return to Top-Level" from the Restarts menu under Eval.  If you type :reset in Franz, (or :q in AKCL, or "Restart the toplevel loop" in MCL) you will end up back in the main Lisp environment from whence you started.  It's really no big deal, just use the :stella command if available or else the (stella:stella) function to reenter Stella's interpreter.

1.5 Quitting Stella

To quit Stella and return to the general Lisp environment, use the Quit command.  But note that since Stella is a Lisp listener, and also contains a number of general purpose Lisp commands under its Cl command loop, we only need to quit to gain access to some Lisp command in Allegro Common Lisp not covered by Cl's command set.


2. Loading, Browsing and Listening

In this first musical example we will Load an existing stella archive file, use the List and Show commands to examine what we loaded, and then Listen to the material in various ways.  You might consider using the Help command on List, Show and Listen in conjunction with reading this section.

To load the example files for this tutorial, you must know where the tutorial directory resides on your machine (hopefully, its the same directory as where you found this file).  For example, at ZKM the tutorial directory is /ZKM/Projects/cm/stella/tutorial.  At CCRMA, it is /Local/Lisp/cm/stella/tutorial.  If you are using MCL Lisp (Macintosh), note that folders in a Macintosh pathname are delimited by the colon (:) character.  You can also select  Load from MCL's Eval menu to load a file.

2.1 Load

To start things off, we use the Load command to load our first example file into Stella. (If you are not already running Stella, type (stella:stella) to enter the main command loop first.)

	Stella [Top Level]: load /ZKM/Projects/cm/stella/tutorial/opus1

	Loading /ZKM/Projects/cm/stella/tutorial/opus1.stella
	Syntax set to MIDI

	Stella [Top-Level]: 

By executing the Load command in the above example, we have loaded an archive file into Stella.  During the loading process Common Music's output syntax was automatically set to MIDI.  Note that the Load command assumed the .stella file extension, so we only need to specify an extension if we want to load a .lisp, .clm or .fasl file instead.  (Actually, a .stella file is also lisp code, and can be edited or compiled just like a normal .lisp file.)

2.2 List

Now that we have loaded something into Stella, let's take a look at what we have.  To do this, we use the List command:

	Stella [Top-Level]: list
	Top-Level:
		  1.	#<THREAD: Opus1a>
		  2.	#<THREAD: Opus1b>
	
	Stella [Top-Level]: 

What are we looking at?  We invoked the List command without any command arguments, so by default the contents of the current focus object were listed.  Our current focus is the Top-Level container, so we see a listing of the objects it contains.

The format of a listing is as follows.  The container that the List command is currently listing is shown in the first column:
	Top-Level:
And each indented line:  
		  1.	#<THREAD: Opus1a>
		  2.	#<THREAD: Opus1b>
shows the position (1 based) and the object at that position in the listed container.  The odd #<> notation in the printout tells us that we are looking at a Stella object of some type.  Each type of object in Stella may have its own notion of how to print itself, but for objects that are containers (objects that hold or produce other objects), the first word in the display tells us what type of container we are looking at, the second word tells us the name of the container.  So in our example, we have listed two Threads (a Thread is a container that yields its contents in sequential order) and their names are Opus1a and Opus1b. Note that there are several other important types of containers:  Merge, Algorithm and Generator. More about these later.

2.3 Show

Now that we have seen the contents of the Top-Level container, let's take a closer look at one of these objects:

Stella [Top-Level]: show opus1a

Object:   Opus1a
Type:     Thread
Status:   Normal
Elements: 25
Start:    -unset-

Stella [Top-Level]: 

The Show command prints detailed information about the attributes of one or more objects we specify as arguments to the command.  In the example above, we see in the first two lines information that we have already discussed while introducing the List command.  The third line shows us the status of the object.  Objects can have various states, for example, they might be hidden, deleted, frozen, etc.  The last two lines are only printed if the object being shown is capable a container of some sort.  The Elements line shows how many elements the object currently contains.  The last line displays the start time of the container, which in our case is unset.

As a further exercise, you might consider trying the Show command without any command arguments, or specifying the other thread (Opus1b).

2.4 Listen

Now that we have learned a little about List and Show, let's go on and Listen to our two threads. First, we listen to each thread separately:

Don't go any further unless your midi device is ready to go and you know which serial port your midi interface is connected to.

Stella [Top-Level]: listen opus1a
If you are using AKCL (you lucky dog!), give this very first start time offset a big number, say 15 seconds.  Thereafter you can use whatever the examples show.
Start time offset: (<cr>=None) 6
Open midi on port: a
Stella [Top-Level]: 
...after six seconds we start hearing our first thread.,We wait until the example is though and then continue...
Stella [Top-Level]: listen opus1b
Start time offset: (<cr>=None) <cr>
...we accept the default (no time offset) and start hearing opus1b immediately
Stella [Top-Level]: 

A few possible questions you might have at this point:

· Why did the system prompt for the midi port in the first Listen but not in the second?  

Since the first Listen was the first time we listened to any MIDI output whatsoever, Common Music needed to know which serial port the MIDI interface is connected to.  (On the NeXT the choices are A or B, on the Macintosh you may call them A or B, Phone or Printer if you want to.)

· Why did we specify 6 seconds in the first example, when we see in the second example that its possible to start listening immediately? 

One reason is to demonstrate that we can specify arbitrary start time offsets to the objects we listen to.  A start time offset specified to the Listen command is only temporary, ie it only affects the current listening pass.  As we will see in the next example, this is very useful for experimenting with shifting musical material relative to each other without affecting any actual musical structure.  However the real reason we specified 6 in the first listening pass is unfortunately more mundane and has to do with the underlying implementation (PCL) of the object system we are using (CLOS).  The very first time the Listen command is invoked, PCL needs a few seconds to build output methods and perform internal bookkeeping tasks.  Because the Listen command process MIDI in "real time", we should give our initial Listening pass some "headroom" so that CLOS can do its bookkeeping tasks and keep up with the real time demands of the music as well. Subsequent listening passes may start whenever they want to, without any interference from CLOS.

· Why didn't I hear anything? 

Is your midi device connected to your midi interface? Are both powered on? Did you pick the right port? Are your amp and speakers powered up? Can you see a midi message arriving at your midi interface? At your midi synthesizer?

In the next example we Listen to both threads simultaneously:

Stella [Top-Level]: listen opus1a,opus1b
Start time offset: (<cr>=None) <cr>
...we accepted the default (no offset) and start hearing both examples in parallel
Stella [Top-Level]: 

And finally, let's listen to both threads in parallel, but offset Opus2b by two seconds:

Stella [Top-Level]: listen opus1a,opus1b
Start time offset: (<cr>=None) *
Opus1a start time offset: (<cr>=None) <cr>
Opus1b start time offset: (<cr>=None) 2

Stella [Top-Level]: 

Note that we have answered the start time offset prompt several different ways. When we accepted the default, no offset was applied to the objects we were listening to.  When we specified a number, it became the number of seconds to offset the start times of all the objects.  When we specified the token *, we were stepped through all the objects and prompted for their individual offset times.

2.4.1 About listening

The Listen command processes one or more containers in parallel, such that the output we hear is the sorted combination of the events produced by the specified containers.  In other words, the final output is the merge of the containers we specified.  To produce this effect, the Listen command just uses an object called a Merge that you may use directly as part of your own musical structure.  We can place merges anywhere we want to, for example, as elements inside threads, and we can merge the output of any type of container.

The Listen command behaves differently according to the output syntax in effect.  For the Midi syntax, the Listen command sends musical material directly to the Midi driver in real time.  In CLM, the Listen command calls clm instruments directly, such that a sound file is written without first producing a .clm score file. This does not happen in real time! In the rosy future when the Listen command works for the Music Kit, musical material will be sent directly to the DSP.  The Listen command will never work for CSound.

There are several other important commands related to Listen. Write is just like Listen, except that it writes a score file to disk and then optionally plays it. The Write command works for all Common Music output syntaxes.  SListen is like Listen, except that the specified containers are listened to in sequential order.  You may optionally specify that a rest (pause) be placed between the containers that you want to listen to.  As you might have guessed, Slisten simply uses a thread object to process the material in sequence.

2.4.2 About time

Every object in Stella has a time slot which, when output processing is initiated by a command like Listen or Write, is set by the system to the "current output time" as the object is processed.  The time slot is read-only, ie its value is managed by the system and should not be set by the composer.  Since objects have no notion of absolute time they may be copy/pasted or structure shared without any temporal problems.  To manage time, the composer works with the rhythm slot for Elements and the start slot for Containers that are to be run inside a Merge.  The rhythm slot is exactly the same as in Common Music: it is a time increment (in seconds) to the next element, whatever that might happen to be.  The start slot for containers specifies a "global start time" for the elements in that container when the container is processed in a Merge.  The start slot actually controls the local start time of the container.  Local time in a Merge aways begins at 0.  The actual clock time an object is processed depends on the local start time and the time at which its container first began to be processed, called its time offset.  So for example, if an algorithm was scheduled to begin at time 10, and its containing Merge was encountered at time 50, the time slot in output events from the algorithm would begin at clock time 60.  Note that the start slot is used in conjunction with containers in merges; the start time of a container inside a thread is ignored because a thread, by definition, processes its elements one after the other.  A Rest object may be used anywhere to increment time without any associated output.

2.5 More about referencing

One of the more powerful features of Stella's editor is its ability to easily reference not only the objects and structures that have been defined in the system, but virtual subsets from these as well. All the editing commands are able to handle virtual object sets as easily as actual structure.  This capability is necessary to support flexible command application, a requirement for any editor trying to support a process as complicated as composition.  To be useful, a score editor must not only be able to reference a container or its subelements, but to be able to reference, say, every fifth element in a subrange of a container.  For example, a composer might need to transpose the frequencies of every fifth element of the middle sixty objects in a container.  A more complicated but no less real requirement is the ability to reference groups of objects in parallel. It is very often the case that a composer is concerned with the state of affairs between events, rather then within them.  For example, a composer might need to know all the locations where three adjacent notes contain ascending frequencies, or to insert rests between every fourth and fifth object in a subrange if the current pair share identical values for some attribute

At this point, we have listed and listened to Opus1a and Opus1b in the Top-Level container.  However, we have not yet seen any of the musical data that we have listened to!  Since we already know something about the List command, let's build on this knowledge to learn a bit more about object referencing.  Keep in mind that everything we learn about referencing here can be applied to other commands as well.

2.5.1 Absolute vs relative referencing 

In this next example, we look at the contents of Opus1a by specifying its name to the List command:

Stella [Top-Level]: list opus1a
Opus1a:
	  1.	#<MIDI FS4 0.5 0.5 0.5 131273661>
	  2.	#<MIDI C5 0.5 0.5 0.5 131274711>
	 ... listing elided
	 24.	#<MIDI E6 0.5 0.5 0.5 131306671>
	 25.	#<MIDI F6 0.5 0.5 0.5 131307241>
Stella [Top-Level]: 

Before continuing, note that when midi data is printed out it looks different than when a container is printed out.  In particular, there is no name associated with each midi-note object and instead we see several fields of data.  The printout for midi-notes appears as follows:
	#<MIDI note rhythm duration amplitude id>
The italicized names are the slot names of the object whose values are printed.  The object id is not really important, but it does supply a unique tag so that objects whose printout data are identical can nevertheless be distinguished from one another.

Referencing an object through its name is called an absolute reference, because there can be only one object with that name.  However, its also possible to reference one or more objects relative to a particular container.  This is where the notion of a focus container comes in handy. For example, we could list the contents of Opus1a simply by specifying Opus1a's ordinal position in the current focus container:

Stella [Top-Level]: list 1
Opus1a:
	  1.	#<MIDI FS4 0.5 0.5 0.5 131273661>
	  2.	#<MIDI C5 0.5 0.5 0.5 131274711>
 ... listing elided

Stella [Top-Level]: 

In general, we can replace the absolute name of an object by its relative "path" from another object.  So in the examples that follow we can always substitute the number 1 for the name Opus1a (or 2 for Opus1b) as long as we remain inside the Top-Level container.

2.5.2 Referencing subelements

What if we only wanted to list a subset of Opus1a, for example, just its first element?

Stella [Top-Level]: list opus1a[1]
Opus1a:
	  1.	#<MIDI FS4 0.5 0.5 0.5 131273661>

Stella [Top-Level]: 

The typical case for referencing multiple objects and virtual subsets occurs when referencing subelements of a container.  To specify subelement references to commands, an array-like syntax is used in which the container reference (absolute or relative) is qualified with a subelement specification denoted inside []  following the container.  For example opus1a[1]is a reference to the first object in a container named opus1a, and 2[1:10,15] is a reference to the first ten objects and the fifteenth object in the second object of the current focus container.  The full-blown notation of a subelement reference is four fields in length with fields delimited by colons: low:high:step:width, where low is the low bound of the subelement reference, high is the inclusive upper bound, step is the stepping increment between objects in the reference and width is the number of objects to reference in parallel.  The entire reference itself is delimited by space or comma if there are more than one of them.  It is only necessary to specify enough of the fields to adequately describe the reference.  The editor currently distinguishes between five basic types of subelement reference: index, a single object.; range, all the objects between a low and high bound; iteration, all the objects spaced a step>1 apart between a low and high bound (a range is really an iteration with a stepping increment of 1); grouping, all the width>1 clusters of objects spaced some step increment apart between a low and high bound (an iteration is really a grouping with a width of 1); sequence, combinations of all the above delimited by comma or space.

2.5.3 Wildcard and shorthand notations

Since subelement referencing inside a [] can get quite complex, the editor supports a few notation shortcuts.  There are several wildcard characters that can be used to designate part or all of the first two fields in a subelement reference.  The wildcard token * has two meanings.  If * is used by itself, it stands for all the subelements, no matter how many there are.  If * appears as the upper bound it stands for the last element, no matter what its position is.  Alternately,  the token end may be used to refer to the last element in a container.  End also supports "subtraction" ie. the token end-4 would mean the element four positions before the end position.  Field specification in a reference can be abbreviated if the default value for a field applies.  In this case only the delimiter for that field need appear in the notation.  The default values for the four fields are 1:*:1:1.  For example, ::2 would stand for 1:*:2 and defines an iteration over every other subelement, and the notation :56::4 is slightly better than 1:56:1:4 and defines a four element group ranging over every position between the first and the fifty-sixth subelement.

2.5.4 A few simple examples

To see the first three elements of Opus1a, we could type:

Stella [Top-Level]: list 1[1:3]
Opus1a:
	  1.	#<MIDI FS4 0.5 0.5 0.5 131310101>
	  2.	#<MIDI C5 0.5 0.5 0.5 131311131>
	  3.	#<MIDI B4 0.5 0.5 0.5 131311501>

Stella [Top-Level]:  

To list every 3rd element between the first and the tenth elements of Opus1a we would type:

Stella [Top-Level]: list opus1a[1:10:3]
	Opus1a:
	  1.	#<MIDI FS4 0.5 0.5 0.5 136172101>
	  4.	#<MIDI C5 0.5 0.5 0.5 136174051>
	  7.	#<MIDI E5 0.5 0.5 0.5 136175341>
	 10.	#<MIDI FS5 0.5 0.5 0.5 136176631>

	Stella [Top-Level]: 

To list the first, fifth through tenth, and sixteenth element of Opus1a:

Stella [Top-Level]: list opus1a[1,5:10,16]
Opus1a:
	  1.	#<MIDI FS4 0.5 0.5 0.5 131310101>
Opus1a:
	  5.	#<MIDI CS5 0.5 0.5 0.5 131312421>
	  6.	#<MIDI D5 0.5 0.5 0.5 131312771>
	  7.	#<MIDI E5 0.5 0.5 0.5 131313341>
	  8.	#<MIDI F5 0.5 0.5 0.5 131313711>
	  9.	#<MIDI G5 0.5 0.5 0.5 131314261>
	 10.	#<MIDI FS5 0.5 0.5 0.5 131314631>
Opus1a:
	 16.	#<MIDI G5 0.5 0.5 0.5 131317411>

Stella [Top-Level]: 

What if we wanted to compare the first 2 elements in Opus1a with the first 2 elements in Opus1b?:

Stella [Top-Level]: list 1[1:2],2[1:2]
Opus1a:
	  1.	#<MIDI FS4 0.5 0.5 0.5 131310101>
	  2.	#<MIDI C5 0.5 0.5 0.5 131311131>
Opus1b:
	  1.	#<MIDI FS4 0.5 0.5 0.5 131340011>
	  2.	#<MIDI E4 2.5 2.5 0.5 131340361>

Note that in this last example we used a comma to delimit two main references so that they would be parsed as a single command arguments by List.   To list from the twenty-third position to the end of Opus1a we would write:

Stella [Top-Level]: list opus1a[23:*]
Opus1a:
	 23.	#<MIDI D6 0.5 0.5 0.5 131322541>
	 24.	#<MIDI E6 0.5 0.5 0.5 131323111>
	 25.	#<MIDI F6 0.5 0.5 0.5 131323461>

Stella [Top-Level]:

2.5.5 A few examples of shorthand

As further exercise, observe what happens when you use the List command with the following references:

list opus1a[*]
list opus1a[::4]
list opus1a[:8::2]
list opus1a[::4:3]
list opus1a[1,end,4:7,10:14::2,end-5,2::3]

2.5.65 Referencing containers vs subelements

Note that for some commands like List, we can also refer to all the elements of a Container simply by referencing the Container itself, so Opus1a and Opus1a[*] reference the same set of objects. Other commands, however, operate on containers and elements independently, so the two references would not be the same.  For example, we can either Hide a container or any of its objects.  (Hide marks the specified objects as unavailable for output, ie they are "skipped over" during output event output, as if they didn't exist.  It is much more efficient to Hide a container than to Hide all its objects.)


3. Creating material using Copy and Paste

There are many ways to create musical material in Stella. We could Load it from an existing archive, use the New command to create it from scratch, describe its computation using the element, thread, merge, algorithm or generator macros, Record it via Midi, or Import it from a score file.  However, the first (and easiest) way we consider is to Copy preexisting structure, Paste it somewhere else, and then alter slot data using the editor's various editing commands.

3.1 Copy Paste

We use the Copy command to place one or more objects in the editor's Pasteboard container. The contents of Pasteboard may then be replicated any number of times using the Paste command.  Any type of references may be copied; the paste command replicates the entire contents of Pasteboard and placed the copies at the specified position or range.  For now let's start out simply by copying just a single thread:

Stella [Top-Level]: copy opus1a
Copied Opus1a to Pasteboard.

Stella [Top-Level]: list pasteboard
 	Pasteboard:
     1. #<THREAD: Opus1a>
	
	Stella [Top-Level]:  
	
Remember, at this point no new material has actually been created, we have simply placed the Opus1a object in the copy buffer. Next we replicate Opus1a by pasting it to the Top-Level container:

Stella [Top-Level]: paste

Paste position: (<cr>=Top-Level) <cr>
Paste Opus1a to new name: opus2a
Pasted Opus2a to Top-Level.

Stella [Top-Level]: list
Top-Level:
     1. #<THREAD: Opus1a> 
     2. #<THREAD: Opus1b> 
     3. #<THREAD: Opus2a> 

Whenever we paste, we must specify a target container position, the place at which the Paste command should insert the copied material.  To append to the current contents of a container, specify the container name as the position.  Othewise, specify the exact container[index] or container[range] to insert the copied material in.

3.1.1 About Paste

There are a few things to know about the Paste command as it relates to the different types of containers.  Paste prompts for a new name only in the event that Pasteboard currently holds a single container.  Otherwise the system generates new names automatically for the containers it finds.  These names can then be changed using the Rename command.  If a Thread or Merge is pasted, both the container itself as well as its objects, including any sub containers, are copied.  If a Generator is pasted, its contents are copied to a new thread.  The Generator's program is not copied.  An algorithm cannot currently be pasted.


4. Simple structure and data modification

At this point our new thread Opus2a is an exact duplicate of Opus1a.  We can modify this material in two ways. The first way would be to change the structure of Opus2a, for example, by reordering its elements, or deleting or adding elements to the current set.  The second way would be to effect changes in the slot values of the data, for example, giving new values to the rhythm, duration, note or amplitude slots of our midi-notes.

Let's pick the easiest command from each category to learn about.

4.1 Retrograde

The Retrograde command reverses the order of objects (or of a specified range of objects) in a container.  In this example we list the first and last elements of Opus2a (just to see what they are) then Retrograde Opus2a and finally relist to confirm that what was once the first element is now the last element:

Stella [Top-Level]: list opus2a[1,end]
Opus2a:
	  1.	#<MIDI FS4 0.5 0.5 0.5 131162121>
Opus2a:
	 25.	#<MIDI F6 0.5 0.5 0.5 131214401>

Stella [Top-Level]: retrograde opus2a

Stella [Top-Level]: list opus2a[1,end]
Opus2a:
	  1.	#<MIDI F6 0.5 0.5 0.5 131214401>
Opus2a:
	 25.	#<MIDI FS4 0.5 0.5 0.5 131162121>

Stella [Top-Level]:

Now let's listen to Opus2a together with Opus1a:

Stella [Top-Level]: listen 1,3 0,1.5

Note that in this last example we specified both the objects and their start times directly on the command line.  Try listening several more times, providing different start time offsets, or listening to all three threads together or in various combinations and offsets.

4.2 Transpose

The Transpose command shifts one or more slot values in an object to new positions in the standard scale.  By default the standard scale is bound to the chromatic scale, but we could substitute any scale made by Common Music's defscale macro.  Note that the process of transposition does not change the nature of the material that was transposed: note names remain symbols, floating point pitches remain floating point, and integer degrees remain integer.  In the following example, we transpose all the material in Opus2a down 2 octaves and a 4th to Middle C:

Stella [Top-Level]: transpose opus2a note -29

Now listen to the material again.  As an additional exercise, consider trying the Invert command on Opus2a.  Invert is similar to Transpose, except that instead of a relative shift amount, we supply an absolute inversion point in the standard scale.  The slot values are then "reflected" around this inversion point:

Stella [Top-Level]: invert opus2a note c4

After this inversion Opus2a has become a retrograde inversion of Opus1a.

4.3 About data editing

Stella has a whole family of commands that effect changes in the data descriptions of objects that define musical material.  All of these editing commands share a similar syntax:

	command reference slot expr slot expr ... 

The only thing slightly new in this syntax is the fact that, in addition to a reference, we now supply object attributes, or slot names, as well.  In other words, it isn't enough to specify which objects to transpose, but also which slot values in these objects should be transposed.   As a rule, whenever a data command expects a slot expr pair we may specify more than one pair.   The syntax of slot expressions (exprs) is complex and will be covered later in this tutorial.


5. New, Go and Set

In this section we learn how to create material using the New command, how to change the editor's focus object using Go, and how to edit musical data using the Set command.

5.1 New

We start out by creating a new thread to hold material that we will create in the subsequent example:

Stella [Top-Level]: new thread
Name for new Thread: (<cr>=Thread-243) opus2b
Container position: (<cr>=Top-Level) <cr>

Stella [Top-Level]: list
Top-Level:
	  1.	#<THREAD: Opus1a>
	  2.	#<THREAD: Opus1b>
	  3.	#<THREAD: Opus2a>
	  4.	#<THREAD: Opus2b>

In this example we created a new thread named Opus2b, and placed it at the end of Top-Level container.  We could have placed it anywhere, ie as an element inside a different container.   We could also have chosen any name that does not yet identify an object in the system.  The editor will always make up a name that is sure to be unique, but it's never a very informative name, so its a good idea to take the time to think up something more descriptive.

Our new thread has no elements yet, so next we use the New command to create some midi data:

Stella [Top-Level]: new midi-note
Number of midi-notes to create: (<cr>=*) 12
Slots and values: amplitude .2 rhythm .5
Container position: (<cr>=Pasteboard) opus2b

Stella [Top-Level]:

Note that in these last two examples, the New command behaved slightly differently when it created midi data than when it created Opus2b.  When creating containers, New simply asks for a name and position for the new object.  But for musical data we have control over how many objects to create, and what initial slot values the data will have.  The default container position is also different.  When we are working in the Top-Level container and we create musical data, the assumption is that we want to place the new material in the copy buffer so that we can quickly replicate it any number of times via the Paste command.

We will revisit the New command again later in order to see what happens if we accept the default value of * for the number of midi-notes to create, but first let's go on to edit the data we just created.

5.2 Go

We have created 12 new midi-note objects and given their amplitude and rhythm slots the values of .2 and .5, respectively.  Let's now change some values in this data.  Since we will be working with the elements of Opus2b, it makes sense to move our focus to this container so that we can refer to the data without having to always specify the container as well.  We use the Go command to move the editor's focus to a different object:

Stella [Top-Level]: go opus2b

Focus:    Opus2b
Type:     Thread
Status:   Normal
Elements: 12
Start:    -unset-

Stella [Opus2b]:

After the Go command executed, the system automatically performed a Show and printed information about our target.  The prompt also changed to reflect the fact that Opus2b is our new focus.  At this point we are "inside" Opus2b, and relative references are resolved using this container rather than Top-Level.

5.2.1 About Go

Go is the main command for moving the focus to a new object. There are several other positioning commands to be aware of.  The Next command moves to the next object at the same level.  Previous moves us to the previous object at the same level.  The Up command moves to the parent container (if there is only one), or to Top-Level if there is none.

Now that we have moved to Opus2b, let's list the data:

Stella [Opus2b]: list
Opus2b:
	  1.	#<MIDI -unset- 0.5 -unset- 0.2 135571461>
	  2.	#<MIDI -unset- 0.5 -unset- 0.2 135572031>

	 ... listing elided
	 11.	#<MIDI -unset- 0.5 -unset- 0.2 135576101>
	 12.	#<MIDI -unset- 0.5 -unset- 0.2 135576451>

Stella [Opus2b]: 

Oops!  Notice that in all of our data, the note and duration slot values are printed as -unset-.  This is because we forgot to specify values for these slots when we created the elements.  If we tried to listen to this material now we would receive an error because the Midi output syntax does not yet have enough information to send to the driver.  We need to set values for the duration and note slots in all our material.

5.3 Set

The main command for setting slot values in musical data is called Set.  We have actually used this command before, we just didn't realize it.  When the New command prompted for slots and values, it actually called the Set command with our input.  So anything we learn here may also be applied to creating new material as well.

We first use Set to specify values for all our duration slots:

Stella [Opus2b]: set * duration $rhythm
12 elements mapped.

Stella [Opus2b]: 

What's going on here?  In this example we mapped over all the elements in Opus2b and, for each element mapped, set the duration slot to whatever the value in the rhythm slot was.  The expr $rhythm means "the value of the rhythm slot in the currently mapped object".  The $name notation may be used anywhere in an expr to refer to the value of the slot name in the currently mapped element.  Perhaps it is unclear why $rhythm had a $ but duration didn't? Remember, the data setting commands expect pairs of: slot expr.  Duration is simply the slot we want to set.  The $ notation in exprs allows us to distinguish between slot references and lisp variable references, which are also legal. So $rhythm is an expr that is evaluated to produce a value for each element we map over.  The expr could also have been (+ $rhythm .25), for example.  Note that when we type exprs we use Lisp's prefix syntax.

Next, we set the note slot in all our data:

Stella [Opus2b]: set * note (notes c2 cs d in heap)

In this example we see that an expr can also be an item stream description.  In the event of an item stream description, the expression evaluator creates the item stream and automatically uses Common Music's item function to read successive items for each object mapped.  This means that wherever we supply a constant expr, we can also supply a dynamic expr that varies each time an element is mapped.  For example, our earlier transposition:
	Stella [Top-Level]: transpose opus2a note -29
might have been specified as:
	Stella [Top-Level]: transpose opus2a note (items 12 -12)
in which case the material in Opus2a would have been transposed in a cyclic pattern of octave up and down motion.

5.3.1 About Set

The Set command is the main command for setting slot values in musical data.  In addition to Set, Transpose and Invert, which we have already used, there are three other closely related commands to be aware of.  The Increment command increments (adds) the current value of a slot by the value the expr evaluates to.  To decrement a slot value, simply specify a negative increment.  If Increment maps over a slot that is unset (has no value) the slot is set to the increment value itself.  The Scale command scales (multiplies) the current value of a slot by the value the expr evaluates to.  If the slot is currently unset, it will be set to the scaling value itself.  It should be obvious that you can only use Increment and Scale with slots that contain numerical values, and that Invert and Transpose only work with slots that contain frequency values.  Finally, the Map command implements a general mapping facility.  All data editing commands are available as mapping clauses. Map is complicated and will be discussed in a later section.  At some point you should read the help on Map to learn about the syntax and evaluation of slot expressions.

5.4 New revisited

Before finishing this section, let's look once more at the New command to see how we can specify all the command on the command line and create as many objects as our slot expr data needs:

Stella [Opus2b]: new midi-note * opus2b note (steps 1 2 in random from 'c2 for 12) amplitude .5 rhythm (rhythms e q in random) duration $rhythm

Stella [Opus2b]:

In this example we selected the default value * as the number of elements to create, which told the New command to create as many midi-notes as was necessary given our subsequent slot value description.  When the number of elements to create is specified in this manner, the system uses the period length of the first slot expr to determine when to stop creating elements. In our case the period length was 12, but this could have been any number, possibly chosen at random.


6. Saving material

At this point it would be a good idea to save our work.  We use the Archive command to store objects in a file on disk.

6.1 Archive

In the next example we move back to the Top-Level container and save all the objects we've worked with so far in a file named "examples.stella" under our home directory:

Stella [Opus2b]: up

Focus:    Top-Level
Type:     Container
Status:   System
Elements: 4

Stella [Top-Level]: archive *
Archive to file: (<cr>=/user/hkt/test.stella) examples
Archiving /user/hkt/examples.stella

Stella [Top-Level]: 

The Archive command allows us to store an output syntax along with the archive. The default file to save objects to is called "test.stella" in your home directory.  The file name you specify is merged against this pathname, so you only need to specify the portions of the name that differ from the default.

6.1.1 About archiving

As of this date (21.9.92), the Archive command is not very bright.  In particular, it won't check for circularities (which you shouldn't have anyway...), nor will it notice that an object referenced by more than one object or occurring more than one time inside a single container (called structure sharing), is actually the same object.  In addition, saving Algorithm or Generator objects that use lexical closures may also cause problems.  Then again it might work.  In any case, the Lisp code that creates the algorithm or generator can always be saved in a lisp source file.  Many of these problems will be fixed sometime in the rosy future.


7. Describing music algorithmically

Up to now we have used commands in Stella's editor to create musical data or objects that hold musical data.  But the editor is really just a "front end" to the system.  Anything you can do in the editor you can also do from the general Lisp environment via function calls.  In fact there are some things you can do from Lisp code that you cannot do by simply using editor commands.  Algorithmic description of music is one of these things.  Algorithms are programs, and as such, are written in Lisp.  Stella comes with a number of predefined functions and macros to help with algorithmic design.  The process is reasonably straightforward and if you have worked with Common Music's with-part, then you should have no problem understanding what to do in Stella.

When working with these functions and macros we are writing Lisp code, so the easiest method is to work inside a text editor and then either save the code as a lisp file to load into Stella, or else copy code and paste it directly to Stella's command prompt from the text editor.

7.1 Referencing objects in Lisp code

When working with Stella objects in lisp,we do not have access to the editor's referencing mechanism for accessing our material.  To reference an existing container from Lisp code, use the dispatch macro #!name, where name is the name of the container. In this example we set the variable foo to the Top-Level object itself, and then use Lisp's describe function on the contents of foo:

Stella [Top-Level]: (setf foo #!top-level)

#<CONTAINER: Top-Level> 
Stella [Top-Level]: (describe foo)

#<CONTAINER: Top-Level> is an instance of class #<Standard-Class CONTAINER 40404511>:
 The following slots have :INSTANCE allocation:
 ID          TOP-LEVEL
 FLAGS       49152
 TIME        "unbound"
 OFFSET      "unbound"
 START       "unbound"
 ELEMENTS    ((#<THREAD: Opus1a> #<THREAD: Opus1b>) (#<THREAD: Opus1b>))

Stella [Top-Level]: 

To reference a particular object in a given container, use the nth-objectfunction:

Stella [Top-Level]: (nth-object 0 foo)

#<THREAD: Opus1a> 

Stella [Top-Level]: 

Note that nth-object indexing is zero based, whereas the editor is 1 based.

You will find most of the editing and mapping functions defined in Stella's source file: "edit.lisp".

7.2 Object constructors

The most important interface to algorithmic composition is a set of predefined object constructor macros.  A constructor macro creates an object given a description of that object.  The constructor macros in Stella may be used to create either elements or containers, or to redefine containers if one with the same name already exists.  The following five sections discuss the various constructor macros in detail.

7.3 object

The constructor macro to create musical data is called object:

object class &rest initializations

class is the type of element to create.  initializations is any number of slot initialization arguments (including none) in the form of slot and value pairs.  For each slot and value pair, the slot name is never evaluated and the value is always evaluated.

Stella [Top-Level]: (object midi-note rhythm .1 note 'c4)

#<MIDI C4 0.1 -unset- -unset- 136713151>

Stella [Top-Level]:

7.4 thread

The thread macro creates an instance of a Thread container, and is defined as:

thread name options &body body

name is the name for the new Thread.  options is a list of zero or more slot initializations in the form of slot and value pairs.  Currently, the only initialization you should supply for a Thread is start.  If start is specified it becomes the permanent start time (until you change it) for the new thread.  Following the options list comes the body of the Thread definition.  Any lisp code is legal here.  Normally, this code would create other elements or containers.  Objects created inside the body of the Thread definition will automatically be added as elements to the thread in the order they are created.  For example, this next piece of code would create (or redefine) a thread named Test to hold ten midi-note objects.  The value for the note slot in each midi-note is selected from a random note stream.

(thread test ()
  (doitems (x (notes c4 d ef f g in random for 10))
    (object midi-note note x rhythm .1)))

7.5 heap

The heap macro creates a Heap container object, and is defined as:

heap name options &body body

The syntax of heap is exactly the same as for thread, but it returns a heap object . A Heap is a type of thread that, when called upon to produce its objects, first shuffles them to produce a random ordering.
  
7.6 merge

The merge macro creates an instance of a Merge container and is defined as:

merge name options &body body

The arguments are the same as for thread.  Note, however, that since only containers can be placed as objects inside a Merge, the body should contain Thread, Algorithm or Generator definitions.  

In this example code we create a merge named test-merge that schedules two subthreads.  The thread named sub1 defines two notes.  Sub2 creates its data by reading ten random notes from an item stream.

Stella [Top-Level]: (merge test-merge ()
                      (thread sub1 () 
                        (object midi-note note 'c3 rhythm .5
                                duration 1 amplitude .7)
                        (object midi-note note 'fs3 rhythm .5
                                duration .5 amplitude .7))
                      (let ((rhy .1)(dur .2)(amp .5))
                        (thread sub2 () 
                          (doitems (x (notes c4 d ef f g 
                                             in random for 10))
                            (object midi-note note x rhythm rhy
                                    duration dur amplitude amp)))))

Stella [Top-Level]: listen test-merge 2

Stella [Top-Level]: 


7.7 algorithm

The algorithm macro creates an instance of an Algorithm container.  An Algorithm computes zero or more elements given an element class specification and a program for changing the slot values as each new element is created.  Actually, it only appears as if an algorithm is creating elements, what is really happening is that a single object is side effected each time the algorithm is called to produce the next object.  Since an algorithm does not maintain a history of what it creates, this is much more efficient than creating an object and then throwing it away after it has been processed.  Creating an algorithm does not automatically generate elements.  This is a feature, not a bug!  Elements are generated at ouput time (for example when using Listen or Write to compute output), when the algorithm is actually needed, rather than at evaluation time.  The algorithm macro is defined as:

algorithm name element-class options &body body

name is the name of the new (or redefined) Algorithm.  element-class is the type of element that the algorithm should generate.  Any class that the object macro can create a single instance of, the algorithm macro can create multiple instances of.  options is a list of zero or slot initializations in the form of slot and value pairs.  There are a number of options that you can set for Algorithms:
length
The number of elements to create before stopping.  This may also be specified as events for backward compatibility with with-part.
start
The local start time of the Algorithm.  This may also be specified as time for backward compatibility with with-part.
end
The local end time of the Algorithm.
rhythm
A time increment used to compute the next run time of the Algorithm.  The value of rhythm is automatically passed to the element generated.

In addition, any initializations that apply to the elements the algorithm produces may also be placed in the options list:

Stella [Top-Level]: (algorithm pulse midi-note (length 64
                                                rhythm .1
                                                duration .1)
                      (setf note
                        (item (notes c4 d ef f g af bf c5
                                     in random)))
                      (setf amplitude
                        (interpl (mod count 8) 0 .25 7 .75)))

#<ALGORITHM: Pulse>

Stella [Top-Level]: listen pulse 2

Stella [Top-Level]:

7.8 generator

The generator macro creates an instance of a Generator container and is defined as:

generator name note-class options &body body

The generator macro has the exact same arguments as algorithm.

The essential difference between an Algorithm and a Generator is that a generator copies and caches each object it generates into a Thread.  This means material is algorithmically generated but also available for editing afterward.  Algorithms, on the other hand, do not cache their data, so you cannot edit this material after the algorithm has been run.  (This is not quite true.  If the algorithm was used to Write a score file, then we could Import the score file back into Stella.)  Element caching is as follows.  When a Generator is run, it first checks to see if it already has elements.  If it does, then the Generator accesses its elements in sequential order, just like a Thread.  However, if the Generator does not have any elements, or if the Generator has been marked "unfrozen", the current elements (if any) are thrown away and a new set will be created and cached in the thread the next time the generator is run.

In this next example, we create a Generator that uses random selection.  We listen to it two times to notice that the the material is identical.  We then unfreeze the generator and listen to new material:

Stella [Top-Level]: (generator mygen midi-note (amplitude .5)
                      (setf note (item (steps 1 2 3 in random 
                                              for 10 from 'c4)
                                       :kill t))
                      (setf rhythm (item (rhythms s e q 
                                                  in random)))
                      (setf duration rhythm))

#<GENERATOR: Mygen> 

Stella [Top-Level]: listen mygen 2

Stella [Top-Level]: listen mygen 0
...we hear the same material both times we listen
Stella [Top-Level]: unfreeze mygen

Mygen unfrozen.

Stella [Top-Level]: listen mygen 1
Start time offset: (<cr>=None) <cr>
...we hear different material

Stella [Top-Level]: 

7.9 Creating structure in loops

There are a few things to note about creating named containers inside loops.  Consider this first example code, which doesn't work:

(merge noodle ()
  (loop for p in '(c4 c5 c6)
        for s from 0
        do
    (let ((off p))
      (algorithm looper midi-note (start s amplitude .1)
        (setf note (item (intervals 0 2 3 5 7 8 in random from off)
                         :kill 10))
        (setf rhythm (item (rhythms e s 32 in random)))
        (setf duration rhythm)))))

The composer wants to create three new Algorithms to run inside a Merge.  Each Algorithm should generate a different series of notes by providing its interval stream with a different value of the varible named offset.  This code would work except for one problem:  we want to create more than one algorithm object, but our algorithm macro uses a single, unevaluated symbol for a name!  If we used this code as it stands, we would create an algorithm name Looper three times, instead of creating three different algorithms.

7.9.1 name

The system provides a function called name that can used in place of a symbolic name (looper in our example above) to specify a new name for each algorithm in a loop.  Name is defined as:

name name &optional new

where if new is nil (the default) the value returned is name, otherwise if new is true (non-nil) then a new name will be generated with name serving as the root of the new symbol.  So our fixed example looks like:

(merge noodle ()
  (loop for p in '(c4 c5 c6)
        for s from 0
        for n in '(nood1 nood2 nood3)
        do
    (let ((off p))
      (algorithm (name n) midi-note (start s amplitude .1)
        (setf note (item (intervals 0 2 3 5 7 8 in random from off)
                         :kill 10))
        (setf rhythm (item (rhythms e s 32 in random)))
        (setf duration rhythm)))))


Try evaluating the code yourself and  then Listen to Noodle.  Maybe give a second or two head start the first time it is run.

7.10 About algorithms

When we evaluated the example code above, we caused a loop to iterate over some values and create three algorithms.  Once the iteration was complete and all the algorithms had been created, the loop finished executing and we were left back at the Lisp prompt.  But there is a problem here!  Inside the body of the algorithms there is a reference to the let variable offset.  But the loop -- and hence the variable offset -- no longer exist!  In fact its even worse. Each of the algorithms refers to not just offset, but to a unique value of offset.  How can these algorithms hope to work? 

7.10.1 Lexical closures

A lexical closure is something in Lisp that allows a function to run in the same environment that it was created in, even if that environment "no longer exists".  Algorithms and Generators use lexical closures to allow their code to execute even after the variable bindings referenced in the code no longer exist.  There are two important consequences of using lexical closures to be aware of:

· We don't have to reevaluate an algorithm definition each time we want to simply generate a new series of elements. Just run the algorithm again.  Generators are a bit more complex; since they remember the last generated elements, we must also unfreeze a Generator before running it again to make new elements afresh.

·Algorithms and Generators that reference lexically free variables will not be able to save the state of those variables when archived. Lexical closures are not part of the Common Lisp standard, so there is no simple, standard way to save them.   Of course, we normally save complicated code that creates algorithms or generators in lisp source files, so it shouldn't be that big an issue.

7.10.2 The vars declaration

A vars declaration may appear as the first form in the body of an algorithm or generator to declare variables that are local to the object and (re)initialized once each time the object is scheduled to begin its output processing.  Vars is defined as

vars &rest bindings

The syntax of a vars declaration is similar to the let* declaration's binding list in Lisp.  Each form in a vars declaration can be either the name of a variable, or a binding list (name value), where value is the initial value of the variable.  Variables in the vars statement are processed in sequential order, so a variable binding can reference a previous variable's value.  For example,

	(vars a (b 2) (c (* b 3)))

would declare 3 locals variables: a b and c. Each time the algorithm is scheduled to begin running, a will be initialized to nil, b to 2, and c to 6.

Here is a comparison of the vars statement with the other two possible ways to declare variables.  The example code segment:

(algorithm foo midi-note ()
  (vars (x (random 10)))
  (print x)
  ...)

uses vars to declare a variable called x that receives a random value initially, just before the algorithm is scheduled to run.  In contrast, the code segment:

(algorithm foo midi-note () 
  (let ((x (random 10))
    (print x)
    ...)

declares a lexical variable x that will be bound to a random value each time the generator computes a note, and the code segment:

(let ((x (random 10)))
  (algorithm foo midi-note ()
    (print x)
  ...)

declares a lexical variable x that is treated as a free variable inside the algorithm (a lexical closure) and is bound to a random value once, when the algorithm is created.

Here is an example of the vars declaration used inside an algorithm called ritard:

(algorithm ritard midi-note (amplitude .9)
  (vars (len (between 5 20))
        (cnt 0))
  (setf note (item (notes c4 d ef for len) :kill t))
  (setf rhythm (interpl cnt 0 .1 (1- len) .3))
  (setf duration rhythm)
  (incf cnt))

In this example, two local variables len and cnt are declared. Each time ritard is run, len is initialized to an integer value between 10 and 20, and cnt is initialized to 0. The value of len is used to compute the length of a note stream, so each time ritard is run it may output a different number of notes before it kills itself.  The cnt variable is used to compute a ritardando as it ranges in value from 0 to len-1. (We could also have used the object's Count slot for this computation instead of the local variable cnt).  Copy/paste this algorithm into Lisp and then:

Stella [Top-Level]: slisten ritard
Start time offset: (<cr>=None) 2
Number of times to sequence: (<cr>=1) 5
Length of pause between selections: (<cr>=None) 1

Stella [Top-Level]: 

Here is a more complicated, but very elegant, example written by Tobias Kunze (tkunze@mvax.kgw.tu-berlin.de)

;;; A simple third-order recursive cellular automaton.
;;; The algorithm maintains a three-element circular buffer 
;;; to compute each new note based on the formula:
;;;
;;; (+ last (* (- 12 (abs x)) (cond ((>= x 0) -1) (t 1))) 1)
;;; 
;;; where x represents the interval from the oldest to the next-
;;; to-oldest (last) note.  Sounds best with a softly 
;;; reverberated percussive (vibe, harp or piano) sound.  Set 
;;; length to some higher number (ca. 1000 or more) to see that 
;;; this generates up to 24 different patterns in lots of 
;;; different phrases

(algorithm cell midi-note (length 200 rhythm .1 duration .5
                                  note 60 amplitude .5)   
  (vars (buf (make-array 3 :initial-contents '(60 60 60))))
  ;; compute width of next-to-last interval and convert to 
  ;; inverse complementary form
  (let ((x (- (- (aref buf (mod count 3)) 
                 (aref buf (mod (1+ count) 3))))))
    (if (>= x 0) 
        (incf x -12)
      (incf x 12))
    ;; now transpose to last note. if the new note is out of
    ;; bounds (< 36, > 98), then shift it back into the valid
    ;; range and increment by 2. otherwise, increment by 1 
    (incf x note)
    (setf note 
      (cond ((< x 36) ; shift up 1-6 octaves + 2 steps
             (+ x (* 12 (+ 1 (random 5))) 2))  
            ((> x 98) ; shift down 1-6 octaves - 2 steps
             (- x (* 12 (+ 1 (random 5))) -2)) 
            (t (+ 1 x))))
  ;; store current note by overwriting the oldest note 
  (setf (aref buf (mod count 3)) note)))


Copy/paste this definition to lisp, then listen to it.  Give it a few seconds if this is the first algorithm you listen to. 

Stella [Top-Level]: listen cell
Start time offset: (<cr>=None) 1

Stella [Top-Level]: 

7.11 Creating structure dynamically

The various constructor macros like merge, thread and algorithm can be used to create containers that we then "execute" or "process" using commands such as Listen or Write to produce musical output.  But what if we want to create structure during output processing?  In order to do this, we must delay the creation of objects until musical time 0, or after, in the output processing.  We call this dynamic because it effects changes in the run time environment, rather than in the static, or "timeless" top level environment we normally work in.  Creating structure dynamically is not much different than at the top level, except that in addition to defining the new objects we must also link them into the current run time environment, represented by the currently executing merge.

To create containers dynamically we use an algorithm object to serve as our delegate for creating new structure during output processing.  Though this algorithm could itself be making sound in addition to creating new objects, w normally would like our delegate to be a "silent partner", ie to not produce sound directly, but rather, to "compute and sprout" new containers that are then executed to produce sound.  In all other respects our delegate should behave like any other algorithm.  Creating a silent algorithm is quite simple, we simply specify the value nil as the element class specification to the algorithm's definition  A nil element class (nil means none, or null, in Lisp) causes an algorithm to be silent because it doesn't know what type of output to create.   The algorithm therefore executes, but is itself silent, or mute.  For example, the form:

(generator nothing nil (length 10) 
  (setf rhythm (item (rhythms q e h in random))))

would create a muted algorithm that would execute 10 times and then terminate. On each output cycle it would randomly select a value for its rhythm slot from an item stream. (Of course, this example is a completely useless algorithm: it neither makes sound nor creates structure.)  Because a silent algorithm is potentially a very powerful compositional aide, there is a a constructor macro called mute that creates them.

7.11.1 mute

The mute macro is defined as:

mute name options &body body

and creates a silent algorithm.  Except for the fact that a mute cannot make sound directly it behaves just like any other algorithm.  This next example defines a mute called foo that displays the current values of its time and count slots to the terminal:

(mute foo (length 4 rhythm .5)
  (format t "~%Count=~S, time=~S" count time))

Stella [Top-Level]: listen foo
Start time offset: (<cr>=None) <cr>
foo executes but we don't hear anything
Count=0, time=0.0
Count=1, time=0.5
Count=2, time=1.0
Count=3, time=1.5
Count=4, time=2.0

Stella [Top-Level]: 

7.11.2 Sprouting new structure

Now that we have a silent mechanism for creating new structure during output processing, we can create objects and schedule them to start executing at or later than the current time.of our delegate.  To create new objects inside the mute, we can simply use the constructor macros we already know about.  In the vast majority of cases, the objects we define inside a mute will be algorithms that generate sound.  To schedule new algorithms to actually begin executing, we use the sprout function, which is defined as:

spout object &optional (queue *merge*)

The sprout function takes an object and inserts in the the current queue, which defaults to the currently executing merge.  (Unless you really know what you are doing, and since you are reading this tutorial you probably don't, you should never supply the optional second argument).

Note that, since we are computing and then spouting new algorithms based on run time decisions, we probably don't want these algorithms to become named, persistent structure in our Top-Level container.  To create an "ephemeral" algorithm, specify nil as its name.  Since the algorithm then has no id associated with it, it will not be appear as permanent structure to the system when it is created, and so it will be garbage collected by Lisp after its run time processing has concluded.  

Here is a simple example of a mute called ma being used to sprout 6 new algorithms.  Each time ma executes it binds the variables off to a new pitch offset and rep to a value between 10 and 20, and then sprouts a new algorithm.  Each new algorithm, in turn, uses the current values of off and rep in the computation of its notes and amplitudes, even though these variable values no longer exist when the algorithm actually start running!

(mute ma (rhythm 2)
  (let ((off (item (degrees c3 c4 c5 ) :kill 2))
        (rep (between 20 30)))
    (sprout
       (algorithm nil midi-note (rhythm .2  duration .175  
                                 start (+ time (random .05))
                                 amplitude .5)
         (setf note 
           (item (intervals 0 2 3 5 7 8 9 in heap 
                            from off for rep returning note)
                 :kill t))))))

Stella [Top-Level]: write ma
Output file:(<cr>=/user/hkt/test.midi) 
Start time offset: (<cr>=None) 
Play file /user/hkt/test.midi? (<cr>=Yes) 

Note the use of Write instead of Listen in the above example.  Creating objects is a fair amount of work for the system.  Creating and scheduling new objects while also producing musical output in real time can bog down quickly if the code is complicated.  If you want to try Listen, maybe give it lots of head start, particularly the first time you try it.

7.11.3 A more complicated example

Here is an even more complicated example that demonstrates mute, sprout and loop all in one fell swoop.  Unfortunately, that is its only redeeming feature -- it sounds pretty awful.

(mute rain ()
  (setf rhythm (item (rhythms q h h. w in random)
                     :kill 2))
  (let (pitch)
    (setf pitch (note (between 100.00 300.00)))
    (loop for beg from 0 by 1.5
          repeat (+ 1 (random 4))
          do 
      (format t "Sprouting new algorithm at ~A to ~A~&" 
              (+ time beg) (+ time beg 5))
      (sprout
        (algorithm nil midi-note (start (+ time beg)
                                  end   (+ time beg 5)
                                  amplitude .25)
          (setf note (transpose pitch (+ -3 (random 7))))
          (setf rhythm (+ .05 (random .15)))
          (setf duration rhythm))))))

Stella [Top-Level]: write rain
Start time offset: (<cr>=None) <cr>
Output file:(<cr>=/user/hkt/test.midi) <cr>
Sprouting new algorithm at 0.0 to 5.0
Sprouting new algorithm at 1.0 to 6.0
...output elided
Sprouting new algorithm at 12.0 to 17.0
Sprouting new algorithm at 13.5 to 18.5
Play file /user/hkt/test.midi? (<cr>=Yes) 
;;; Playing: /dist/cm/midi/playmidifile -f /user/hkt/test.midi  &
[1] 10661

Stella [Top-Level]: 

8.	 Recording and importing material

In this next section we examine two more ways to create material using the Receive and Import commands.

8.1 Receive

The Receive command is a general midi input utility.  When we receive raw midi data, there are several possible things we might do:  send the data directly to the midi output device, ie play it in real time, record it to a thread for later editing, mixing or listening, or catch the data and perform some arbitrary processing using a specified catch function.  In this section we will examine just the recording option.

Recorded midi data may be used to describe any class of element in Stella, not just Midi-Notes.  For example, when working with CLM, it is still possible to use midi key number,timing and velocity information to produce basic parameter data for a clm instrument.  Once midi data has been recorded, the Receive command steps through a number of prompts that allows us to transform raw midi data into whatever representation fits our current need.

In this next example., we record a new thread of midi-notes and then listen to it in combination with Opus1b:

Don't perform this example unless you have a midi input device of some sort hooked up to your computer and its ready to be used.  This example assumes that you are using a midi keyboard and will signal the end-of-recording by depressing the A0 key (keynum 0)

Stella [Top-Level]: receive
Receive mode (Record, Play, Catch): (<cr>=Play) record
Stop recording by key, time or count? (<cr>=Key) <cr>
Key value: (<cr>=A0) <cr>
Type <cr> to begin recording messages: <cr>

Start playing Louie Louie. When you've had enough fun, press the A0 key to stop recording.

Save recording? (No, or name of new thread): rec1
Element type for recorded data: (<cr>=Midi-Note) <cr>
Keep channel info? (No, or slot to set): (<cr>=Channel) <cr>
Keep keynum info? (No, or slot to set): (<cr>=Note) <cr>
Keynums become: (<cr>=Note) <cr>
Keep velocity info? (No, or slot to set): (<cr>=Amplitude) <cr>
Convert velocity to amplitude?: (<cr>=Yes) <cr>
Use On/Off pairs for duration? (No, or slot to set): (<cr>=Duration) <cr>
Use On/On pairs for rhythm? (No, or slot to set): (<cr>=Rhythm) <cr>
Container position: (<cr>=Top-Level) <cr>

Stella [Top-Level]: list
Top-Level:
	  1.	#<THREAD: Opus1a>
	  2.	#<THREAD: Opus1b>
	  3.	#<THREAD: Rec1>

Stella [Top-Level]: listen 1,3
Start time offset: (<cr>=None) <cr>

Stella [Top-Level]:

In this example, we used Receive's record mode to create a new thread called Rec1 containing midi-note representations of our recorded improvisation.  Before the actual recording began, we specified a signal that stops the recording process.  We chose the keynum method, but we could also have specified an ending time (in seconds) or a total number of messages to receive.  Once the recording was over,we saved the material in a Thread called Rec1.  Before actually creating any structure, the record mode stepped us through a number of options that affected how the raw midi data was processed.  Our most important decision was the element type to create out of the recorded data.  Since we are working with the Midi output syntax, we simply chose the default Midi-Note class, but we could have specified any class defined in Stella, for example the MusicKit's Pluck object or a CLM object that we created ourselves.  Once we specified the class of object to hold our material, we were prompted for how to process the actual data from the various midi message fields.  We could have discarded any information we did not need.  Since we are working with Midi-Notes we kept all of the data, but transformed it in several ways.  We saved midi keynum data as note names because this representation is much easier to read.  We could also have saved keynum data as floating point pitches, or kept it as integer degrees.  We then specified that midi velocity information (0-127) should be transformed into normalized amplitude (0.0-1.0).  Finally, we specified that the on/off and on/on timing information between messages should be transformed into clock time (seconds) values for the duration and rhythm slots in our Midi-Notes.  Once we were through specifying the transformations, the Rec1 thread was created and placed in the Top-Level container.  We then listened to our new material in combination with Opus1b.  At this point our recorded material may be treated just like other material we have already created in earlier sections.

8.2 Import

The Import command parses a score file and creates objects to represent the data and structure contained in the score file.   Import is essentially the "reverse process" of Write.  Once the score file has been parsed and objects created, the material from the score file is just like any other material in Stella.   Currently, the Import command only supports the MusicKit and the MIDI output syntaxes.  CLM and CSound will be added soon.

In this next example we import "noodle.midi" (which should be located in the same directory as this tutorial) and then listen to it.

Stella [Top-Level]: import
File to import: /dist/cm/stella/tutorial/noodle.midi
Stella [Top-Level]: list
Top-Level:
	  1.	#<THREAD: Opus1a>
	  2.	#<THREAD: Opus1b>
	  3.	#<THREAD: Rec1>
	  4.	#<THREAD: Thread-5445>
	

Stella [Top-Level]: rename 4 udon
Stella [Top-Level]: list udon[1]
Udon:
	  1.	#<MIDI C4 0.5 0.5 0.09448819 131044611>	

Stella [Top-Level]: listen udon
Start time offset: (<cr>=None) 

Stella [Top-Level]: 

The Import command will soon support options that allow the separation of channel or instrument material into different threads.  In this case, the score file will be represented as a Merge containing the various channel or instrument Threads.


9. Map

The Map command is a general purpose utility for exploring and editing musical material.  For example, what if we wanted to know what the highest note and the average rhythm was of all the elements in Udon?

This example assumes you have actually imported "noodle.midi" in the previous section and renamed the thread to Udon.

Stella [Top-Level]: map udon highest $note and average $rhythm
you might notice a momentary pause the first time map is used.  it runs at full speed thereafter.
CLAUSE           COUNT  VALUE

highest  $note     180  GS6
average  $rhythm   180  0.10138889

Stella [Top-Level]: 

In this example, we specified an object reference followed by a conjunction of two mapping clauses.  Map examined all the elements in Udon, gathered two pieces of information in parallel and returned the results in a table.  Each entry in the table shows the relevant command clause, followed by the number of times it was processed and the value it returned.  Our first table line says that the highest note in Udon is GS6 and that the command processed this clause 180 times.  This number might be less than the total number of elements mapped if the clause was not applicable for all the data.

9.1 Mapping clauses

The syntax of the Map command is unlike any other command we have seen so far.  Map implements a small "mapping language" consisting of a series of clauses.  Each clause may be independent or conditional.  A clause generally consists of an operator and an expr to evaluate in the context of the currently mapped element.  There are three broad categories of clause operators.  Information operators collect information about the objects we map over and return the results in a table.  Command operators allow all the Top Level data processing commands to be used inside mapping clauses.  Clause operators connect clauses together.  Here is a summary of the various of mapping clauses and their syntax:

9.1.1 Information clauses

collect expr		return the values of expr in a list
sum expr			return the sum of expr
count expr		return number of times expr is true
minimize expr	return the minimum value of expr
maximize expr	return the maximum value of expr
lowest expr		a minimize for scale references
highest expr		a maximize for scale references
average expr		return the average value of expr
find expr			return positions where expr is true

  
9.1.2 Command clauses

set {slot expr}+			perform top-level set
scale {slot expr}+			perform top-level scale
increment {slot expr}+	perform top-level increment
transpose {slot expr}+	perform top-level transpose
invert {slot expr}+		perform top-level invert
insert object				place object before current
append object				place object after current
insert-at {pos object}+	place object before pos
append-at {pos object}+	place object after pos
delete object				mark object as deleted
undelete object			undo delete
hide object				mark object as hidden
unhide object				undo hide
print expr					print value of expr to terminal
do expr					evaluate expr

9.1.2 Clause operators:

and					conjunction of 2 or more clauses
when expr clause 	evaluate clause if expr is true 
unless expr clause	evaluate clause if expr is false 
while expr clause 	returns if expr is not true 
until expr clause		returns if expr is true 

9.3 A few more examples

To find out the total length of time the elements in Udon occupy:

Stella [Top-Level]: map udon sum $rhythm

CLAUSE           COUNT  VALUE

sum      $rhythm   180  18.25

To count the number of notes above fs5:

Stella [Top-Level]: map udon count (scale> $note 'fs5)

CLAUSE                       COUNT  VALUE

count    (scale> $note 'fs5)   180  81

Stella [Top-Level]: 

To collect every 20th note in a list:

Stella [Top-Level]: map udon[1:*:20] collect $note

CLAUSE         COUNT  VALUE

collect  $note     9  (C4 GS4 F4 C4 C6 GS4 DS6 C6 DS6)

Stella [Top-Level]: 

9.4 Conditional mapping

By including conditional operators such as when and unless, the Map command may be used to gather information or edit material only in the event that some condition holds true or false in the currently mapped object.  That is, the conditional operators cause one or more clauses to be evaluated only if some test evaluates to true (not nil) or false (nil).  For example, what if we wanted to know how much rhythmic time was occupied by notes higher than fs5?:

Stella [Top-Level]: map udon when (scale> $note 'fs5) sum $rhythm

CLAUSE           COUNT  VALUE

sum      $rhythm    81  8.625

Stella [Top-Level]: 

Note that is is possible to make more than one clause conditional on a single test, and that it is possible to specify mixtures of conditional and independent clauses.  In this example, we count the note and sum the rhythms of objects higher than fs5, but we sum the durations of all the elements:

Stella [Top-Level]: map udon when (scale> $note 'fs5) count $note and sum $rhythm sum $duration

CLAUSE             COUNT  VALUE

count    $note        81  81
sum      $rhythm      81  8.625
sum      $duration   180  49.875

Stella [Top-Level]: 


In this final example, we edit material conditionally by scaling the amplitudes of all the objects that have either c4, fs4, or c5 as their note:

Stella [Top-Level]: map udon when (member $note '(c4 fs4 c5)) scale amplitude 1.5

Stella [Top-Level]: listen udon
Start time offset: (<cr>=None) 

Stella [Top-Level]: 


10. A complete example

As a final exercise we will build up a short section of music by working with many of the commands we have learned about so far.  Output from this example can be found the in the file "section.midi" in the tutorial directory 

We start off by importing the tutorial file noodle.midi to a thread called T1 and listening to it:

You much change the path /cm/stella/tutorial/ in the import statement below to the correct tutorial directory pathname for your machine.

Stella [Top-Level]: import /cm/stella/tutorial/noodle.midi
Stella [Top-Level]: rename top-level[end] t1

When listening to this material it will probably sound best if you can find a percussive sound like a piano on your midi device.

Stella [Top-Level]: listen t1
Start time offset: (<cr>=None) <cr>

Now we start building up our section by copying T1 to a new thread called T2, then transposing the material in T2 up one octave, and then listening to both our threads with the start time of T2 offset by 5 seconds:

Stella [Top-Level]: copy t1
Copied T1 to Pasteboard.

Stella [Top-Level]: paste top-level t2
Pasted T2 to Top-Level.

Stella [Top-Level]: transpose t2 note 12

Stella [Top-Level]: listen t1,t2 0,5

Stella [Top-Level]: 

Next we edit the material in T2 to give it a slightly more interesting effect:

This must remain a single line of input!
Stella [Top-Level]: map t2 scale amplitude 2 when (member $note '(g5 gs5 g6 gs6 g7 gs7)) scale amplitude 3 duration 2

Stella [Top-Level]:

What did we do here?  We mapped over the elements in T2 and scaled their amplitudes by 2.  In addition, if the note value of each object was in the set (g5 gs5 g6 gs6 g7 gs7) we scaled the amplitude again (by 3 this time) and doubled the duration.  This highlights a G/GS motive in the material .  Now let's listen to both again to hear the new effect:

Stella [Top-Level]: listen t1,t2 0,5

Stella [Top-Level]: 

Next we build a bass texture by pasting T1 to a new thread called T3, and then transposing T3 down 2 octaves.  Since lower tones require more energy to speak, we also scale the amplitudes in T3 by 5.

Stella [Top-Level]: paste top-level t3
Pasted T3 to Top-Level.

Stella [Top-Level]: map t3 transpose note -24 and scale amplitude 5

Stella [Top-Level]: 

Now we will emphasize just the lowest note in the bass texture.  But in order to do this we first need to find out what that note is:

Stella [Top-Level]: map t3 lowest $note

CLAUSE         COUNT  VALUE

lowest   $note   180  C2

Stella [Top-Level]: map t3 when (scale= $note 'c2) scale duration 2 amplitude 2

Stella [Top-Level]:

Now we listen to all the material together with each voice offset by five seconds:

Stella [Top-Level]: listen t1,t2,t3 0,5,10

Stella [Top-Level]: 

Next we define an algorithm called Chords that will randomly construct pretty cords:
Copy the following code and paste it into the lisp window, then hit <cr>.

(algorithm chords midi-note (amplitude .75)
  (setf note
    (item (notes (chord (notes c6 d ef f g af bf 
                               in heap for 4))
                 (chord (notes c5 d ef f g af bf 
                               in heap for 4))
                 (chord (notes c4 d ef f g af bf 
                               in heap for 4))
                 r)
          :kill 6))
  (setf rhythm 
    (item (rhythms 32 (rhythms e q. h for 1) e e)))
  (setf duration rhythm))


By pasting this code into the lisp window and hitting <cr> we defined a new algorithm object called Chords:
#<ALGORITHM: Chords> 

We now  listen to it by itself a few times until we understand what it does:

Stella [Top-Level]: slisten chords 5 repeat 4 pause 1

Stella [Top-Level]:

Now we listen to all our material together:

Stella [Top-Level]: listen t1,t2,t3,chords 0,5,10,15

Stella [Top-Level]:

Finally, we want make the start time offsets "permanent" and create a new merge called Section to hold all our completed material.  Section could then be used as a single element in a larger compositional structure, which I leave up to you...

In order to set the Start slots of our containers we switch mapping modes by setting the global variable *mapping-mode* to :containers. We then reset *mapping-mode* back to :data after performing our command:

Stella [Top-Level]: (setf *mapping-mode* :containers)
:CONTAINERS

Stella [Top-Level]: set t1,t2,t3,chords start (items 0 5 10 15)

Stella [Top-Level]: (setf *mapping-mode* :data)
	:DATA

Now we are ready to create or new merge called Section, and add our objects to it:

Stella [Top-Level]: new merge section top-level

Stella [Top-Level]: add t1,t2,t3,chords section

Stella [Top-Level]: list section
Section:
	  1.	#<THREAD: T1>
	  2.	#<THREAD: T2>
	  3.	#<THREAD: T3>
	  4.	#<ALGORITHM: Chords>


Finally, we listen to Section and archive it for the greater glory of all mankind.

Stella [Top-Level]: listen section
Start time offset: (<cr>=None) <cr>

Stella [Top-Level]: archive section sec.stella
Archiving /user/hkt/section.stella


11. Defining new objects

In this section we learn how to add our own object definitions to Stella. Once a new object has been defined, we can use it with any command or constructor macro, just as we do with midi-notes.  But in this section we move from Midi to the Common Lisp Music output syntax because CLM is the most logical system with which to demonstrate new object definitions.  Since we will be writing code that extends the functionality of Stella itself, this section assumes some familiarity with Lisp, CLOS and the CLM system.  If this isn't the case, skip this section.

11.1 About Common Lisp Music (CLM)

Common Lisp Music, by William Schottstaedt (bil@ccrma.stanford.edu), is a powerful and expressive sound synthesis language implemented in Lisp and C.  CLM is essentially a "toolbox" for instrument building. Though it comes with a number of predefined instruments ready to use, the whole point of CLM is to provide the composer with the capability of defining instruments from the ground up.  Given this potential, we want to learn how to extend Stella in such manner that we can manipulate data for any new instrument that we we build in CLM.

11.1.1 The FM instrument

Our starting point is a simple FM instrument.  The instrument definition itself is not important; it simply serves as our example of a CLM instrument that we write our Stella interface to.

Copy this code to a text editor and save it in a file named "fm.ins" in your home directory.  (You can find a completed version of "fm.ins" in the same directory you found this tutorial in.)

(in-package :stella)
 
(definstrument fm (start duration frequency amplitude 
                           &key (amplitude-envelope 
                                  '(0 0 25 1 75 1 100 0))
                                (mratio 1) (index 1)
                                (index-envelope '(0 1 100 1))
                                (degree 0) (distance 0)
                                (reverb 0))
  (let (beg end car mod ampf indf ind loc)
    (multiple-value-setq (beg end) (get-beg-end start duration))
    (setf car (make-oscil :frequency frequency))
    (setf mod (make-oscil :frequency (* frequency mratio)))
    (setf ind (in-hz (* frequency mratio index)))
    (setf ampf (make-env :envelope amplitude-envelope 
                         :scaler amplitude :start beg 
                         :end end))
    (setf indf (make-env :envelope index-envelope 
                         :scaler ind :start beg :end end))
    (setf loc (make-locsig :degree degree :distance distance 
                           :revscale reverb))
    (Run
      (loop for i from beg to end
            do
        (locsig loc i (* (env ampf) 
                         (oscil car (* (env indf)
                                       (oscil mod)))))))))

11.2 CL

Once this code has been saved in a file, the file must be compiled and loaded it into Lisp.  We use the CL command to enter a small Lisp utility command loop (CL stands for Common Lisp) under Stella , then type ? to see the possible utility commands, and then compile and load our instrument file using CLoad:

Stella [Top-Level]: cl

Type ? for help.

Lisp> ?

?                   Show this help.
ARGS                Print function arguments.
CC                  Compile code (function spec or value of *)
CF                  Compile a file.
CLOAD               Compile/Load a file.
LD                  Load a file.
MACROEXPAND         Macro expand form.
PACKAGE             Switch lisp packages.
QUIT                Quit Top-Level.

Lisp> cload /user/hkt/fm.ins

; --- Compiling file /user/hkt/fm.ins ---
; Compiling FM
; Writing fasl file "/user/hkt/fm.fasl"
; Fasl write complete
; Fast loading /user/hkt/fm.fasl.

Lisp> 

Note that after executing the CL command, the prompt changed to "Lisp>".  This was to tell us that we left the main Stella program and entered a new command loop with a completely different command set.. Now that we have compiled and loaded our file we could go back to Stella's main loop by using Cl's Quit command, but since we will be recompiling again shortly we will continue to work here a bit longer.

Let's try out our fm instrument:

Lisp> (with-sound () (fm 0 1 440 .25))

After waiting a few seconds you should hear something.  If not, check to see if your computer is muted, or your amplifier is powered on, etc.

Next we try something a bit more complicated:

Lisp> (let ((env '(0 0 1 1 25 .25 100 0)))
        (with-sound () 
          (fm 0 1 440 .25 :index 3
             :amplitude-envelope env 
             :index-envelope env)))

In the two preceding examples the first four parameters values were required by the fm instrument.  In the second example we supplied other parameter values as well, but each of these values were preceded by the :keyword form of the parameter name itself.  The exact parameter syntax of a definstrument is critical when we write an interface to it in Stella.

Now we have defined a CLM instrument and know that it works, we move on to designing our interface.


11.3 About the interface

The interface we are going to write will connect data in Stella with our instrument definition in CLM.  The interface consists of a class definition for an object representing an fm "event" or "note", and at least one output method to write our data somewhere, either an open .clm score file or an open .snd file.  At some point in the rosy future there will be an equivalent of Common Music's defpart macro for Stella that defines this interface automatically, but for now we have to do it by hand.

11.4 The FM object definition

The first thing we do is to define a class of object that represents data for the fm instrument.  To do this we use Lisp's defclass macro.  After we compile and load this definition it will be possible to create fm objects using New, or to Change existing objects to this new class, or to Record midi data and turn it into fm data.  Copy this class definition into "fm.ins" and place it just after the definstrument definition:

(defobject fm (note)
  ((instrument :initform 'fm) 
   duration
   frequency
   amplitude 
   amplitude-envelope
   mratio
   index
   index-envelope
   degree
   distance
   reverb))

The code in this example declares a new class of object called Fm.  The fact that we now have an instrument definition and object definition using the same name does not confuse Lisp.
defobject declares that Fm has the system's Note class as its parent, or superclass.  This means that Fm will receive, or inherit, slots and behavior from the more general Note class. Two of the most important slots that Fm will inherit are the time and rhythm slots. When we work with Fm objects, we will be able to refer to time and rhythm values simply because we declared that Fm was a type, or subclass, of Note.  But in addition to these two basic slots that all Notes share, our Fm object also needs slots for holding fm instrument parameter data.  Since these slots are specific to just our class and not inherited from the Note class, we must declare them in the defobject  form as well. Defobject automatically defines CLOS :initargs for each slot so that we can use them in initialization lists to macros such as element and algorithm.

Note that the "local" slots are, for the most part, identical to the instrument parameter names we declared in our definstrument forms.  But this doesn't have to be the case.  Our interface will be able to map from any object slot name to the equivalent parameter name in the instrument.  In fact, this must be the case because of inherited slots like time.  Time is a special slot maintained by the system for all objects.  We want this value to correspond to the parameter named start in our instrument.  If instead of using time, we were to invent a slot in Fm called start, we could certainly use it to hold information for the instrument parameter, but this slot wouldn't have anything to do with the actual start time of our objects, because that is the function automatically performed by the time slot.  The instrument slot bears mentioning because it doesn't appear in the definstrument parameter list.  The Instrument slot corresponds to the actual Fm instrument name itself.  We define it to be a slot value because it might be useful to use our fm object to generate output events for other instruments with the same parameters as fm.  By default our object will use our fm instrument, but by simply changing the value of the instrument slot we could call a different instrument.  If we had made the instrument name a constant value in our interface we could never have this flexibility.

11.4 write-event

All that remains now is to define the actual interface to the fm instrument. Our interface is defined in terms of output methods for a system function called write-event.  An output method is a procedure for writing data from our object to an open output stream.  Input and output in Stella is defined in terms of input or output event streams.  There are streams to represent score files, sound files, even the midi driver.  A system container named Io-Streams holds all the input or output streams created in the current session.  We can change the characteristics of an ouput stream by editing its entry in the Io-Streams container, or using the Open command.  See section 12.5 for more information.  When we define a new type of object, we often need to define a new method for write-event to handle outputting data from the new object.  Once this is done, these methods will automatically be called by the system whenever the appropriate conditions arise.  Write-event is the only output function used by the system, and is defined as:

write-event object stream

object is the object whose data we want to write.  stream is the open output stream.

As mentioned earlier, we have two possible methods on write-event to consider with CLM. The first is an interface from our fm object to a CLM sound file.  The second interface is from our object to a clm score file, which is really just a lisp file containing expressions that, when evaluated, call CLM instruments with data.  We start with the sound file interface because it is the simplest to understand.

11.5 The sound file interface

Copy the following code into the fm.ins file and place it after the defclass form:

(defmethod write-event ((object fm) (stream clm-sound-file))
  ;; call the clm instrument with our data in the proper format
  (apply (slot-value object 'instrument) 
    (collecting-slots object time duration frequency amplitude
                      &key amplitude-envelope mratio index 
                           index-envelope degree distance reverb)))

In this example we defined an output method on write-event that is invoked whenever the system wants to write data for our object to a sound file.  We do this by calling the instrument directly , passing it object data via the Lisp function apply:

Lisp> (apply '+ '(4 5 6))

15 

Lisp> (setq foo '+)

+ 

Lisp> (apply foo 1 2 3 '(4 5 6))

21 

Lisp> 

The function to apply to our object data is stored as the value of the object's instrument slot, which, by default, holds the symbol FM, which is also the name our definstrument!  Accessing the data we apply to the fm instrument can be fairly tricky.  Luckily, Stella has a macro called collecting-slots that will do all this for us:

collecting-slots object &key slot-lambda

The collecting-slots macro collects slot values and returns them in a list suitable for function calling.  We use collecting-slots to specify which slots we want collect values from and also the format of the returned list itself. Recall that our definstrument has four required arguments followed by a series of keyword arguments.  For those slots listed after &key in the collecting-slots specification, the return list will contain a keyword value pair for each slot that currently has a value.  If a slot does not have a value (unbound) then no entry for it will appear in the list collected.  This is the correct behavior for supplying keyword argument data lisp function.

11.6 The score file interface

(defmethod write-event ((object fm) (stream clm-event-file))
  ;; prints a left paren, slot data, right paren and end-of-line.
  (let ((file (slot-value stream 'stream)))
    (formatting-slots (object file :preamble "(" :postamble ")" 
                                   :eol t)
                      instrument time duration frequency amplitude
                      &key amplitude-envelope mratio index 
                           index-envelope degree distance reverb)))

This method is very similar to our sound file method. In both cases we specify the same slot information in the same format.  The main difference is that, instead of collecting data and calling an instrument, this method on write-event prints data to a file in Lisp function call format suitable for CLM score files.   Formatting-slots is similar to collecting-slots, but provides all sorts of fancy control over the printed representation of an object and its slot values: 

formatting-slots ((object stream &key (printer 'princ) (print-if t)
                                                                eol prefix suffix format filter
                                                                (delimiter #\space) preamble
                                                                postamble constructor default)
                             &body slot-descriptors)

This macro is a bit too involved to explain in this tutorial, see dictionary.rtf for complete details about using formatting-slots, and look in mk.lisp for examples of how the system uses it to define output methods for the Music Kit.


11.7 A print function

The last remaining thing to do is to create a printing method for our Fm class of object. This code is optional, but its very useful to write print-object methods that show current slot values whenever an object is printed to the terminal, for example by the List or Show.  Our print-object method is similar to our score file method for write-event, except that we don't want to display the object as a lisp expression. Instead, we use printing-random-thing to surround our display within #< and > delimiters and to print a unique identifier with our object so we can visually distinguish between two fm object with identical slot value. Our formatting-slots code is basically the same, but with the important differences that print-object displays the rhythm slot instead of time (since the rhythm slot is the one we actually manipulate as composers), and that unbound required arguments do not produce an error when printed but are displayed as -unset-  instead.

(defmethod print-object ((object fm) stream)
  ;; printing-random-thing surrounds output with #< > delimiters.
 (printing-random-thing (object stream)
    (formatting-slots (object stream :default +slot-unset+)
                      instrument rhythm duration frequency amplitude
                      &key amplitude-envelope mratio index 
                           index-envelope degree distance reverb)))


The following example shows print-object output given an Fm instance with an unbound amplitude slot:

Stella[Top-Level]: (element fm frequency 440 reverb .1 
                            duration .1 rhythm .2)

#<FM 0.2 0.1 440 -unset-  :reverb 0.1 132350031> 

Stella[Top-Level]: 

11.8 Fm example

Before we attempt to use our fm instrument and object together, we must first compile and load "fm.ins".  Make sure you have saved the file with all the code listed in this section, or else copy the fm.ins file from the tutorial directory to your home directory.

Lisp> cload /user/hkt/fm.ins

; --- Compiling file /user/hkt/fm.ins ---
; Compiling FM
; Writing fasl file "/user/hkt/fm.fasl"
; Fasl write complete
; Fast loading /user/hkt/fm.fasl.

Lisp> quit

Stella [Top-Level]:

At this point we are ready to try out our new object.  We make a Thread, add some Fm notes, and then listen to it:

Stella [Top-Level]: new thread fm-test top-level

Stella [Top-Level]: new fm
Number of fms to create: (<cr>=*) <cr>
Slots and values: frequency (pitches c4 d ef f g in random for 10) rhythm .1 duration .1 amplitude .25
10 elements mapped.
Container position: (<cr>=Pasteboard) fm-test

Stella [Top-Level]: list fm-test
Fm-Test:
	  1.	#<FM 0.1 0.1 349.2283 0.25 131731731>
...listing elided
	 10.	#<FM 0.1 0.1 261.62555 0.25 131746361>

Stella [Top-Level]: syntax clm

Current syntax is COMMON-LISP-MUSIC.

Stella [Top-Level]: listen fm-test
Start time offset: (<cr>=None) 
Modify new stream /zap/test.snd?: (<cr>=No) 
File: /zap/test.snd
Channels: 1
Srate: 22050.0
Reverb: None

Stella [Top-Level]:

Now experiment with the Write command to test our score file interface.

Note that when we specified data for the frequency slot, we used Common Music's pitches macro rather than notes.  This is because our fm instrument expects its frequency parameter to contain numerical data.  We could change either our instrument or our interface such that note names could be used as well.

12. Manuscripting data using CMN

To work with the examples in this section you must have installed the CMN system and built Common Music with the CMN syntax enabled. 

12.1 About Common Music Notation (CMN)

Common Music Notation, by William Schottstaedt (bil@ccrma.stanford.edu), is a manuscripting program that uses traditional common music notation.   CMN evaluates Lisp expressions like:

	(cmn staff treble c4 q)

and writes a postscript image to one or more .eps files.  The example above would generate a file named "aaa.eps" in your home directory that displayed one staff line with a treble clef and a quarter-note Middle C.  

12.2 The CMN output syntax

The CMN output syntax supports postscript output (or cmn input) from normal event data in Stella, via the CMN program.  The CMN syntax is intended to provide a visual aide for composition as well as a facility for producing raw CMN input files from data produced by algorithms and other types of containers.  It is not a substitute for working directly with CMN to produce manuscript quality output.

When the CMN output syntax is current, the Write command creates either .eps (postscript) or .cmn (cmn input) files.  We write .eps files when we want to see an interpretation of our compositional data in common music notation.   We write .cmn files when we want to create input data to cmn that we can then edit to produce a final, high quality manuscript.  (There is no backward link from .cmn to .stella files, so its best to delay the generation of .cmn input files until your composition is finished and you are ready to start polishing the manuscript.)

12.3 A simple example

In this first example we create a new thread called foo, add four midi notes, and then listen to it. We then set our syntax to cmn and generate a postscript image of our midi data:

Stella [Top-Level]: new thread foo top-level

Stella [Top-Level]: go foo

Focus:    Foo
Type:     Thread
Status:   Normal
Elements: 0
Start:    -unset-

Stella [Foo]: new midi-note
Number of midi-notes to create: (<cr>=*) <cr>
Slots and values: note (notes c4 d ef f g) rhythm 1 duration .5 amplitude .5
Container position: (<cr>=Foo) <cr>

Stella [Foo]: listen foo
Select output syntax: midi

Current syntax is MIDI.
Start time offset: (<cr>=None) 5
Midi port to open: a
We wait for our example to finish playing before continuing. 

Stella [Foo]: syntax cmn

Current syntax is COMMON-MUSIC-NOTATION.

Stella [Foo]: write foo
Output file:(<cr>=/user/hkt/aaa.eps) 
Start time offset: (<cr>=None) <cr>
Modify new stream /user/hkt/aaa.eps?: (<cr>=No) <cr>

Creating cmn score...
Manuscripting /user/hkt/aaa.eps...
Done!
Display file /user/hkt/aaa.eps? (<cr>=Yes) <cr>
In NeXTStep 3.0, Yap should launch with aaa.eps.   You can select another postscript previewer by resetting the variable stella::*cmn-previewer*
Stella [Foo]:  

 12.4 The CMN display object

The system defines an object, called Cmn,  to hold CMN manuscripting variables and function calls.  The Cmn object facilitates the inclusion of simple manuscripting directives in the musical data we define in Stella.  It is not meant to replace the compositional values for frequency, rhythm and amplitude that are part of our normal musical event data.  The Cmn object has a single slot, named Data, which holds a list of forms (cmn variables and/or cmn function calls) that we want to be evaluated as cmn manuscripting directives.  (The nature of CMN variables and function calls are beyond the scope of this tutorial.  To lean more about CMN, read cmn.wn included in CMN's source code).  As a convenient shorthand for the most common usage, the value of the Data slot may also be just a single CMN variable name.  Because a single function call is itself a list, a function call must always be specified as an element in a data list.  Here are a few examples of legal syntax of Data values : 

begin-beam                     ; data consists of a variable
(begin-beam)                    ; data consists of a variable
(begin-beam (meter 2 4))             ; data consists of a variable and a function call
((meter 2 4))                      ;data consists of a function call

Because the Cmn object has output methods defined for only the cmn output stream, we can customize the display of our material without the Cmn objects having any impact on musical output when we switch back to our main syntax for listening or score file generation.  Thus, we can use Stella's editing and mapping commands to add graphic information to our composition without "side effecting" the actual music itself.

We continue with our previous example and add one Cmn object to display our data in 2/4 meter and another to draw a concluding double bar.  Note that we quote the values for the Data slot because these forms are only meaningful as CMN directives, not as Lisp expressions: 

Stella [Foo]: new cmn
Number of cmns to create: (<cr>=*) 1
Slots and values: data '((meter 2 4))
Container position: (<cr>=Foo) <cr>

Stella [Foo]: new cmn
Number of cmns to create: (<cr>=*) 1
Slots and values: data 'double-bar
Container position: (<cr>=Foo) <cr>

Stella [Foo]: list
Foo:
	  1.	#<CMN ((meter 2 4)) 140044131>	
	  2.	#<MIDI C4 1 0.5 0.5 136657171>	
	  3.	#<MIDI D4 1 0.5 0.5 136657221>	
	  4.	#<MIDI DS4 1 0.5 0.5 136657251>	
	  5.	#<MIDI F4 1 0.5 0.5 136657301>	
	  6.	#<MIDI G4 1 0.5 0.5 136657331>	
	  7.	#<CMN double-bar 140070361>	

Stella [Foo]: write foo
Start time offset: (<cr>=None) <cr>
Output file:(<cr>=/user/hkt/aaa.eps) <cr>

Creating cmn score...
Manuscripting /user/hkt/aaa.eps...
Done!
Display file /user/hkt/aaa.eps? (<cr>=Yes) <cr>

Stella [Foo]: 

When viewing the resulting aaa.eps file, note that CMN has also drawn internal bar lines at the appropriate places in the music!  This is because we told CMN that our material was in 2/4 meter.  In its normal operating mode, CMN  automatically makes a whole host of decisions about rules involving metering and layout.  We can control these automatic decisions (and much more) by customizing the CMN output stream, which we will lean about in the next two section. 

12.5 About music streams

Stella performs the input and output of musical event data via "musical streams".  Every type of 
output destination or input source has its own corresponding type of stream to implement reading and writing musical events.  For example, there are streams for connecting to sound files, streams for connecting to score files, even streams for connecting to the Midi driver.  Since streams are fairly "low level" we normally don't deal directly with them, we simply specify an output destination: a file on disk, the Midi driver, etc.  However, there are times when it is appropriate to modify a stream in order to respecify its output behavior.  There are two possible ways to modify streams in the editor.  The first way is to use the Open command to directly specify (or respecify) characteristics of the stream. The second way is to use the Edit command from either the top level or from within the Write command itself.

12.5.1 The Open command

The Open command is the fastest and most concise way to specify characteristics for all types of streams:

Stella [Top-Level]: open midi port a

Stella [Top-Level]: open clm srate 44100 channels 2

Stella [Top-Level]: open aaa.eps size 16 metronome 120

Stella [Top-Level]: open test.score header *header*

Use open to initialize both files and listening streams.  To initialize a listening stream, specify its syntax to the open command, followed by the list of attributes and values you want to set.   To initialize or reinitialize a file, specify its pathname, followed by the list of attributes and values you want to set.  The end of appendix A for a list of streams and their available attributes.

12.5.2 The Write command

To modify a stream is  from within the Write command itself, specify Yes when prompted to "Modify new stream?".  For example, in section 12.3 above, when we created the "aaa.eps" stream, we could have specified Yes instead of No to the prompt line:
 
Modify new stream /user/hkt/aaa.eps?: (<cr>=No) <cr>

Answering Yes would have automatically invoked the Edit command on our new output stream, and we we could then have customized any of the stream's various attributes.

And finally, we can alter any output stream by invoking the Edit command on the stream that we have created and now wish to modify.  But how do we access the stream objects?

12.5.3 The Io-Streams system container

Stella maintains a system container named Io-Streams, which holds all of the streams currently defined in the editor.  Since we normally don't need to work with these objects, the Io-Streams container is not listed as part of the Top-Level display.  However, we can still access this container by name, just like any other container defined in the editor.  For example, we can List the contents of Io-Streams, and use the Show command to examine one of its entries:

The exact contents of Io-Streams depends on what streams have been created in the editor; the listing you see may be different than in this example.

Stella [Foo]: list io-streams
Io-Streams:
	  1.	#<Midi Listener (port: A)>	
	  2.	#<File: "/user/hkt/aaa.eps">

Stella [Foo]: show io-streams[1]

Object:   #<Midi Listener (Port: A)>
Type:     Midi-Stream
Status:   Normal
Slots:
          Flags     0
          Stream    A
          Syntax    #<Syntax: MIDI>
          Port      A
          Direction :IO

Stella [Foo]:

12.5.4 The Edit command

Now that we know how to access an existing stream (by listing Io-Streams and then selecting the appropriate stream by its index) we will learn a bit about the Edit command before actually modify our cmn output stream.  

To edit any object in the system, specify the object (by name or index) to the Edit command.  In this next example we invoke Edit on the Midi Listener stream.  We then type ? to get a command summary, then use the Show command to list the slot values in our Listener, and then Quit back to the top level Stella prompt:

Stella [Foo]: edit io-streams[1]

Editing #<Midi Listener (port: A)>
Type ? for help.

Edit: ?

?                 Show this help.
DIRECTION         Set value of named slot.
FLAGS             Set value of named slot.
PORT              Set value of named slot.
QUIT              Quit Edit.
SHOW              Show slots and values.
STREAM            Set value of named slot.
SYNTAX            Set value of named slot.

Edit: show

FLAGS             0
STREAM            A
SYNTAX            #<Syntax: MIDI>
PORT              A
DIRECTION         :IO

Edit: q

Stella [Foo]: 

Note that the Edit command behaves very much like the top level command loop in Stella.  This is because they are really the same program, they just use different command sets!  Edit builds its command set "on the fly", from the slot names defined in the the object that we want to edit.  To change a slot value in the object we are editing, simply type the name of the slot and its new value as a command to the "Edit:" prompt.  In addition to these "slot setting" commands, Edit also supports the general commands:  ?, Show, and Quit, which we have already read about in the first section of this tutorial.  

Now we can finally get back to the topic at hand, which, you might recall, was the modification of CMN graphic output streams...

12.6 Common CMN stream customizations

As mentioned in section 12.4, the CMN output stream produces either .eps or .cmn output, depending on the file name extension we specify to the Write command.   In addition to selecting what type of output to generate, we can also customize any of the CMN score attributes  listed on page 3 of the CMN manual.  Normally, we don't need to change the vast majority of these score attributes.  However, there are three common situations where we would like to make a few simple modifications.  

The first situation is when we would like to change the overall size of the manuscripted image.  There are lots of score attributes that relate to size, but the most general of them is the Size slot, which controls the overall font size of the manuscript.  In Stella, this size defaults to 24.  To display data using a font size larger or smaller than 24, you would change the value of Size to a larger or smaller number.

The second common customization to a CMN output stream is to reset CMN's "parsing tempo".   The parsing tempo defines how CMN should interpret time values, which are always expressed as floating point seconds in Stella.  To change the parsing tempo, specify a new value to the Metronome slot of the output stream.  This value defaults to the value of *standard-tempo* at the time when the stream was created.  The default value of *standard-tempo* is 60, which means that unless you either change the value of *standard-tempo* before you create the stream, or explicitly set the Metronome slot value in the output stream yourself, CMN will interpret a time value of 1.0 (one second) as a quarter note.  Similarly, .5 seconds will be an eight-note and so on.  So, for example, to redisplay our earlier example with .5 seconds interpreted as a quarter note, we must change the value of Metronome slot in the CMN output stream to 120. 

In the next example we will change both the Size and the Metronome values in our current cmn output stream (aaa.eps) and then recompute the postscript image:

Be sure to specify the appropriate index for aaa.eps in your Io-Streams container.

Stella [Foo]: edit io-streams[2]
Editing #<File: "/user/hkt/aaa.eps">
Type ? for help.

Edit: metro 120

The value of METRONOME is 120

Edit: size 40

The value of SIZE is 40

Edit: q

Stella [Foo]: write foo
Start time offset: (<cr>=None) <cr>
Output file:(<cr>=/user/hkt/aaa.eps) <cr>

Creating cmn score...
Manuscripting /user/hkt/aaa.eps...
Done!
Display file /user/hkt/aaa.eps? (<cr>=Yes) <cr>

Stella [Foo]:  

The third common customization that we might make to a CMN output stream is the specification of "staff descriptions" to further control the display of our data.  By default, every container that outputs data is displayed in its own staff, with its name appearing as the staff label in the CMN manuscript.  In addition, CMN automatically decide which clefs and how many staves should be used.  So we only need to specify staff descriptions:

when we want a staff label to be different than a container's name.
when we want to specify the clef(s) that CMN should use to display notes.
when we want inferior containers to appear in the same staff as the superiors.

All of these situations are addressed using Edit's Staves command with the system's staves macro.  In the next example we create a staff named X and to hold data from our Foo container.  We also specify that CMN should display staff X's using the bass clef.  Then  we quit Edit and regenerate aaa.eps:

Stella [Foo]: edit io-streams[2]

Editing #<File: "/user/hkt/aaa.eps">
Type ? for help.

Edit: staves (staves (foo :name "x" :clef bass))
Edit: q

Stella [Foo]: write foo
Start time offset: (<cr>=None) 
Output file:(<cr>=/user/hkt/aaa.eps) 

Creating cmn score...
Manuscripting /user/hkt/aaa.eps...
Done!
Display file /user/hkt/aaa.eps? (<cr>=Yes) 

the aaa.eps file now displays X as the staff label with all notes appearing in the bass clef, quarter note at 120.

Stella [Foo]: 

See dictionary.rtf for a complete description of the staves macro.

12.7 Advanced CMN stream customizations

All attributes of a CMN score (see cmn/cmn.wn) are available for customization using either the Edit or the Open commands.  Since the number of these attributes is quite large, CMN score attributes other than the common cases of staves, size and metronome must be prefixed with the cmn package name when using Open:

Stella [Top-Level]: {open aaa.eps size 12 metronome 90
                          cmn::redundant-accidentals nil}

Up to now we have only customized attributes implemented directly by CMN.  But there are also three other important score attributes implemented by Stella: meter, staffer and marker.   These attributes don't relate to manuscripting directly, but rather, to controlling the manner in which data is passed from Stella to CMN.  

The simplest of these attributes is meter, which allows us to specify a global meter for CMN to parse our event data into.  For example,

Stella [Top-Level]: open aaa.eps meter (meter 2 4) size 16

would initialize aaa.eps to parse data into 2/4 meter.  A global meter may be overridden for a particular staff by using the staves macro (see dictionary.rtf for further information) 

The most important Stella parsing attribute is staffer, which allows us to control the mapping of event data to their proper CMN staves.  The value of the staffer attribute is a function Stella invokes on each piece of data to determine which CMN manuscript staff it belongs on.  There are currently two staffing functions supplied by Stella, but you can specify your own staffer function to a cmn stream to implement manuscript staffing however you want.   By default, CMN streams use container-staff to map event data to CMN staves.  The effect of this staffer function is to place each event in a CMN staff that "corresponds" to the event's container in Stella.  By respecifying a stream's staffer attribute to be channel-staff, midi events may be placed in CMN staves according to their midi channel information, independant of whatever container they happen to occupy in Stella.  The file stella/scripts/channel.tl is a script file demonstrating CMN output using midi channel information.

The marker attribute holds an optionally specified function that will be called on each piece of data after it has been sent to CMN.  The purpose of a marker function is to implement programatic addition of manuscripting marks to a CMN note that has just been placed in its CMN staff.

As a conclusion to this section, here is an example of a marker function and the Open call I used to output a large section of a string orchestra piece.   The music itself was contained in two generator objects, and consisted of midi data with channel information distributed between 0 and 3,  representing violins 1 and 2, viola and cello, respectively.

;;; this marker adds a wedge to all notes with amplitude > .5

(defun marker2 (stream object)
  (when (and (typep object 'midi-note)
             (>  (slot-value object 'amplitude)  .5))
    (let ((staff (gethash (slot-value object 'channel) 
                          *cmn-staves*)))
      (cmn::add-data-1 stream staff (cmn-eval 'wedge)))))

This open call intialized my output file darkling2cmn to hold a cmn manuscripting expression that I then edited to perfect the manuscript.  The swrite command manuscripted midi data from the two generators into 4 CMN staves according to the channel information contained in the midi-notes. Channel 0 and 1 were notated in the treble celf, channel 2 in alto and treble, and channel 3 in tenor.

Stella [Top-Level]: {open darkling2.cmn staffer channel-staff 
                          metronome 75 size 16 meter (meter 2 4)
                          marker marker2
                          staves (staves (0 :clef treble)
                                         (1 :clef treble)
                                         (2 :clef (alto treble))
                                         (3 :clef tenor))}

Stella [Top-Level]: syn cmn

Stella [Top-Level]: swrite sec2a,sec2b



12.8 Defining your own CMN output methods

Defining an output method for CMN manuscripting is really not any different than defining output methods for any other syntax in Stella.  This discussion assumes that you have read chapter 11, which describes output method definition for Common Lisp Music objects.  

To produce CMN manuscript output you simply need to define a method on write-event which passes parameter data from your object to the open cmn-stream music stream via the  function cmn::add-note-to-staff, which takes as its arguments the stream, the staff of the current object, the current values for time and duration, and the current value for frequency.  For example, here is the output method that the system defines for midi-note:

(defmethod write-event ((object midi-note) (stream cmn-stream))
  (cmn::add-note-to-staff 
    stream 
    (container-staff stream (slot-value object 'container))
    (slot-value object 'time)
    (slot-value object 'duration)
    (cmn-eval (slot-value object 'note))))

Only two functions in this method need an explanation.  Stella objects have no explicit notion of "staff".  This  is a graphic notion, and properly belongs to CMN and the CMN output stream. The link between an object in Stella and its CMN staff is managed by the cmn output stream, which maintains a "dictionary" of staves indexed by object container.  To get the staff of an object, call the function container-staff, passing it the output stream and the object's container.  The other function that need clarification is cmn-eval, which evaluates a lisp expression in the context of the CMN program and insures that (possibly) symbolic data are interpreted in the correct package, so that variables or function names meant for CMN are handled properly.  We filter the value of the midi-note's Note slot through cmn-eval because Note may contain symbolic "note names" like C4 and DS5, which must treated as variables in CMN.  Note that C4 is not a variable in Stella and has no meaning other than as the symbolic name of a scale degree in the standard chromatic scale.  So  we use cmn-eval to insure that the C4 symbol in Stella produces the value of the C4 variable in CMN:

Stella [Top-Level]: , c4
The symbol C4 has no value.

Stella [Top-Level]: (cmn-eval 'c4)

#<CMN::WRITE-PROTECTED-NOTE 35514351> 

Finally, before leaving this section, we make sure to switch back to our main MIDI syntax.  Otherwise, if we were to try to Listen to material while the CMN syntax was still current, we would hear nothing because the CMN syntax has output methods only defined for writing files:

Stella [Top-Level]: syn midi

Current syntax is MIDI.

Stella [Foo]:  


13. Describing musical layout


It is often useful to experiment with different arrangements, or layouts, of compositional material.  We could, of course, allocate merges and threads to do this by hand, but it would be much more convenient to use a simple language for organizing our material.

The layout macro creates an organization of containers (threads, algorithms, etc) that may be referenced by name in the Listen or Write commands.  Unlike constructor macros such as thread, merge and element, the layout macro provides a convenient mechanism for arranging structure that already exists.  By defining different layouts we can experiment with different structural organizations of our material.  For example, given the existence of four containers named A, B, C and D, the forms:

(layout comp1 (seq a b (mix c d@1)))

(layout comp2 (mix a@1.5 (seq b c)@2 d))

would define two different musical organizations of the identical containers. The Comp1 layout would define a sequence of three components: A, B and a mix of C and D together.  The Comp2 layout would define a mix of three components: A, the sequence of B and C, and D.  (As you might suspect, mix and seq, called layout directives, are internally represented by normal merge and thread objects.) The @ appended to some of the layout components defines a start time directive, which may be used to tag a particular component of a mix with an explicit start time. 

13.1 layout

The layout macro is defined as:

layout name directive

where name is the name of the layout, and directive is a single "top level" seq or mix directive:

seq &rest directives
mix &rest directives

The seq and mix directives take any number of sub-directives, which may be the names of existing containers (threads, merges, algorithms and generators) or other seq and mix forms.  Inside a mix directive, any component  may be optionally tagged by a start time directive, @time, which declares the initial start time for the tagged component in its mix. For example, foo@5 would declare a start time of 5 for the foo container, and (seq d a)@.3 would declare a start time of .3 for the sequencing of D and A.  If a time directive is not specified, a component will begins at time 0 in the mix.  It is an error to specify a start time directive for non-mix components.

13.2 A layout example

In this example we create three different layouts, Comp1, Comp2 and Comp3,  out of four threads, named A B C and D, which hold the prime, inversion, retrograde and retrograde-inversion forms of a series item stream.  

Copy the following code and paste it into the lisp window, then hit <cr>.

(let ((stream (series 0 7 8 15 16 11 10 5 6 13 14 21 
                     from (notes c4 fs5 fs4 c4)
                     forming (items p i r ri) 
                     returning note)))
  (dolist (x '(a b c d))
    (thread (name x) ()
      (doitems (n stream)
        (element midi-note note n amplitude .5 
                 rhythm .25 duration .2))))

  (layout comp1 (seq (mix a b) (mix c d@1)))
  (layout comp2 (mix (seq a d)@.75 (seq c b)))
  (layout comp3 (mix (mix b c d)@1.25 a)))

Stella [Top-Level]: slisten a b c d
Length of pause between selections: (<cr>=None) 1
Start time offset: (<cr>=None) <cr>

Stella [Top-Level]: slisten comp1 comp2 comp3
Length of pause between selections: (<cr>=None) 1
Start time offset: (<cr>=None) <cr>

Stella [Top-Level]: 


Lacunae and bugaboos

Stella and Common Music currently exist in separate packages.  This shouldn't create problems, but when I have some time I will redefine with-part, defscorefile and defpart to use the new representations and remerge the packages.

Be careful of using rests in item streams outside of algorithm or generator code. For example, when using doitems to create new objects the R notation is meaningless. Use the Rest object to represent silence.

The Listen command should print information about the sound file it writes in CLM syntax.  This will be fixed in the near future.

You should only specify containers to the Listen command,  To listen to individual notes, use Slisten.  This isn't a bug, but I don't handle it very gracefully.


Appendix A. Predefined Slots and Classes

The following table lists "public" slots in the predefined classes.

A.1  All objects

User slots available to all objects:
time	The current clock time of the object.  Time is read-only and is only valid during the current output execution.

A.2  Container classes

Thread, Merge, Algorithm, Generator

User slots available to all containers:

start	The start time offset (seconds) of the container in its parent Merge.  This may also be specified as time in the initialization list for backward compatibility with with-part.

User slots available to Algorithm and Generator:
length	The number of elements to create before stopping.  This may also be specified as events in the initialization list for backward compatibility with with-part.
end	The local end time of the Algorithm or Generator

A.3  Rhythmic element classes

Note and Rest

User slots available to all rhythmic elements:
rhythm	The time delta (seconds) to the next element in the container.

A.4  MIDI note classes

Midi-Message, Midi-Note

User slots available to all midi objects:
message	The integer midi message.  See "midi.rtf" for more information.  This value is automatically computed for Midi-Notes from the current values of channel note and amplitude.

User slots available to Midi-Messages:
data	a list of sysex data

User slots available to Midi-Notes:
channel	The midi channel number for the midi on/off pair. Defaults to 0.
note	The keynum, note name or floating point frequency of the midi note.  The system automatically converts this to keynums for the on/off pair.
duration	The time (seconds) that the note lasts.  The system automatically converts this value to a schedules note off.
amplitude	The amplitude (0.0-1.0) of the midi note.  The system automatically converts this value to velocity between 0 and 127.

A.5  Music Kit note classes

Wave1vi, Wave1i, Pluck, Mixsounds, Mkmidi, Fm2pvi, Fm2pnvi, Fm2cvi, Fm2cnvi, Fm1vi, Fm1i, Dbwave2vi, Dbwave1vi, Dbfm1vi

The Music Kit classes are identical to those found in Common Music, except that there is no Poly and Mono distinction.  Instead the type and tag of a note are explicitly represented.

User slots available for all Music Kit notes: 
type	The MusicKit note type of the note.  May be one of :duration, :noteOn, :noteOff, :noteUpdate or :mute.  Defaults to :duration.
tag	The MusicKit note tag of the note, if any
part	a part info reference representing the MusicKit part of the note.  It's best to leave this one alone.

For more information about the various Music Kit note parameters, see "SynthPatchLibrary.rtf" in Common Music's doc/contrib directory.

A.6  CMN element classes

Cmn

User slots available to all CMN objects: 
data	A list of variables and function calls to be evaluated in the context of the CMN package.  For convenience, the the data slot may hold just a single variable name.
channel	If you included MIDI when building Stella each CMN data object also has a midi channel attribute, otherwise channel is omitted from the definition.



Appendix B. Commands and Output Syntax

The following table summarizes the current implementation state of commands that are influenced by choice of output syntax.  Y=it works, N=it doesn't and may never, *=not yet.

			Midi	CLM	CMN	MusicKit	Csound
Listen		y		y		n		*			n
Slisten	y		y		n		*			n
Import	y		y		n		y			*
Write		y		y		y		y			*
Swrite		y		y		y		y			*

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.