kernel - University of Washington

advertisement
The Lisp Kernel:
A Portable Environment for Musical Composition
John Rahn
10/31/88
The University of Washington
To appear in the Proceedings of the
First International Workshop on Artificial Intelligence and
Music,
Gesellschaft Fuer Mathematik und Datungsverarbeitung,
St. Augustin, Germany, September 15-16, 1988
(Springer-Verlag, 1989)
Although this paper was invited for and delivered at the
First International Workshop on Music and AI, the software
described herein does not in itself make use of artificial
intelligence per se. Later in the paper there will be some
suggestions for further connections along that line. Another
caveat: though the audience was mostly computer scientists, the
author is merely a musician, who hopes that his efforts may
offer at least some ethological interest to computer scientists.
It may help for such readers to consider this a report from a
native informant in the field of music.
This essay will first outline the basic idea behind the
Lisp Kernel software, its design specifications, and the goals
it attempts to serve. It will go on to describe the current
state of the system, including its basic data structures and
categories of procedures, and what this is capable of doing
now. Next, the paper throws out some ideas for future
development connected with the Lisp Kernel, including some
applications of artificial intelligence. Finally, there are some
remarks on the desirability of writing software that can talk to
other software.
THE ORIGINAL IDEA
The problem
A typical workstation might have input capabilities that
include ASCII keyboard, WIMP, music notation graphics, and
musical keyboard or other instrument via MIDI. Processing this
input data might involve several kinds of commercial software
(MIDI sequencing, music notation, etc.) as well as
non-commercial software such as composing programs and
languages. Some of the software may have been written by the
user. Output from the workstation might take the form of ASCII
text to a file on the local microcomputer or on some larger
computer to which it is networked, or to the screen; printed
music notation (or a PostScript file for a typesetting machine);
MIDI output to a synthesizer; and sound output via any of a
number of hardware and software devices, such as a
special-purpose synthesizer (4X, Platypus), digital signal
processing board, or general music synthesis software such as
Cmix or Music4P. [1] Which input and output devices
specifically are available will vary from workstation to
workstation, and from place to place. Each piece of software
for input, processing, and output will have its own data
structure for representing music. How do we get them all to
talk to one another?
A solution
One approach would be to write a separate interface program
for each pair of programs that need to talk, but this has an
order of growth problem built in. The approach pursued here is
to write a "kernel" that would sit in the logical middle of the
music workstation software (see Figure 1). Such a kernel should
have a data structure for representing music which is general
enough that each of the data structures employed by other
programs could, at least in principle, be translated into it.
It should be able to hold and keep track of the musical entities
it generates or acquires from other programs, and to translate
freely any musical entity in its internal database into a form
acceptable to any program that might use that musical entity,
such as a synthesis program. It seems useful also to include in
such a kernel the capability of generating musical data via at
least one composing language.
A composer could then work within this kernel, moving
without penalty from any particular workstation to any other, so
long as each workstation runs the kernel software. This implies
in turn that the kernel software should be portable across
machines, and across generations of machines so that next year's
hardware does not make it obsolete.
Why Lisp?
Lisp recommends itself as the underlying language for such
a kernel. It is a well-established language with a long
history, whose theory is as well understood as that of any
complex high-level language. It lends itself to large programs.
Its code is compact and (if decently written) very readable. It
itself is a good language to generate musical data with, and it
is well adapted to symbolic processing and to applications such
as writing databases and other languages. Finally, Lisp has
recently become widely available on relatively inexpensive
machines.
There are of course many dialects of Lisp, but is only one
language standard for Lisp, and that is Common Lisp. [2] The
Department of Defence in the United States has interested itself
in the Common Lisp standard, and as a result, most manufacturers
of supercomputers are now porting some implementation of Common
Lisp to their machines. This is important, since most music
applications, and certainly this one, tend to run up against
limits with respect to processing and memory requirements.
Critics of Common Lisp dislike its size, unwieldiness, "kitchen
sink" design strategy, and general ADAfication. However, Common
Lisp is lexically scoped, which makes it in principle compatible
with other important dialects such as Scheme.
The strategy pursued with the Lisp Kernel is to write it in
a Minimal Lisp subset of Common Lisp, with the idea of porting
the Lisp Kernel to other lexically scoped dialects such as
Scheme, T, and Xlisp version 2.0. Xlisp is a public domain Lisp
whose interpreter is written in C, which runs on every
microcomputer and minicomputer imaginable. An Xlisp version of
the Lisp Kernel would then be available to those who could not
afford to buy a Common Lisp for their workstation. The Common
Lisp version could run faster on larger workstation systems and
supercomputers.
Summary
The "portable" in the title of the paper refers to the
intention to make the lisp kernel portable across machines,
across generations of machines, and across lexically scoped Lisp
dialects, to ensure durability and wide availability. The
Kernel is kernelish in its intended logical position in the
midst of a music workstation and its ability to have other
software hung onto or around it, and also in its implementation
within a Minimal Lisp subset of Common Lisp.
THE CURRENT STATE
The current working ingredients of the Lisp Kernel are the
data structure for music representation, a simple database, the
composing language Lispfront, its own simple object-oriented
programming facilities (OOP), the ability to input and output
MIDI data, and the ability to output Music4P score data.
will be described briefly.
Each
The Data Structure
What we are after here is a sort of Universal Solvent.
What is this magical data structure, which is so general, so
powerful, that it can accommodate any other representation of
musical data? Obviously such a quest cannot succeed. What we
have, rather, is a data structure that can accommodate any
event-oriented representation of music. It can accommodate any
of the MusicN synthesis programs, such as Cmix, Csound, and
Music4P; conventional music notation; and MIDI. It takes in
process-oriented or verb-oriented descriptions only insofar as
these can be recast as descriptions in terms of events (e.g. a
process-event, a participle). Graphic descriptions (in the form
of graphic scores) and object-oriented descriptions are subject
to the same restrictions. Time will tell whether such
restrictions are not too onerous, and whether non-standard
representations such as processes will prove to be translatable
in terms of events. However, the intention here is not odiously
to set up any universal standard for music representation, but
to accommodate naturally the largest possible set of widely used
current representations.
It is important that the proposed data structure be
mathematically sound and well-understood, as well as musically
intuitive. If possible, it should generalize easily so that the
entire data-structure may be a hierarchy of homogeneous
substructures.
The Note Structure
The Lisp Kernel employs a its basic data structure
something called a "note structure." (See Figure 2.)
Figure 2
note structure
mapping
{ <p, v>, <p, v>, ...}
where each p (property) is an atom
and each v (value) is a list.
This is simply a set of ordered pairs, such that the first
element of each pair names a property, and the second element
gives a value for that property. Without further restrictions,
this would be a mathematical binary relation in extenso. It
turns out that one loses nothing, and gains much, by imposing
the restriction that the binary relation be a function, or
mapping. In a mapping, no property may have more than one
distinct value associated with it.
The representation of a note or other musical entity as a
function is very suggestive, and lends itself to possible
avant-garde implementations. [3] The Lisp Kernel implementation
is more sedate. In this implementation, each element of the
function's domain is an atom which names a parameter or
property, and each value is a list. A note structure is
implemented as simply a flat list of alternating properties and
values.
The idea is similar to more familiar Lisp structures such
as association lists and symbol property lists. One could
model a Lisp environment by a note structure. In a note
structure, properties are primarily distinguished from values by
position. The primitive Lisp predicates ATOMP and LISTP also
suffice to distinguish them, though the Lisp Kernel makes no use
of such type checking. Rather, the data abstraction provided by
the procedures for constructing, updating, and accessing a note
structure, keeps properties and values straight, and ensures
that any note structure will in fact formally be a set of
ordered pairs that is a function in extenso.
This "note structure" is used throughout the Lisp Kernel at
all hierarchical levels so that there is a consistency and
homogeneity of data structure. Only at the lowest levels would
its meaning be a conventional musical note. It might be helpful
to describe here only the basic accessor, constructor, and
updater procedures.
The basic accessor is called GETV and takes two arguments,
a note structure and a property name, returning the value for
that property. Thus (GETV note property) returns some list that
is the value of that property. The constructors and updaters
are combined, and are in two families. For each family, the
three arguments are a note structure, a property name, and a
value. If the referenced note structure does not have the given
property, the property is appended to the note structure along
with its value, so each updater is also a constructor. The
ASSIGN family replaces anything in the value cell for the
referenced property for the referenced note with the new value,
destroying the old value. The PUTPV family appends the new value
to any pre-existent contents of the value cell for the given
property for the given note. (See Figure 3.) It would be
somewhat more machine-efficient to prepend the new value using
CONS (though in fact these constructors do use CONS, then
reverse the result). Appending the new value allows the user to
make sense of the raw data of the note-structure: the properties
appear in the order in which the user assigned them initial
values.
It may seem odd that a note structure, as a function, can
return a list as the value of a property. Though the list can
contain a number of items, it is the list which is the unique
value of that property. Thus, for example, an ENVELOPE property
might have as its value a list of breakpoints and heights
(point1 height1 point2 height2 ...). There might be a
VISUAL-REPRESENTATION property whose value contains the
information necessary to display the note in conventional
musical notation. In fact there are far woolier possiblities,
none of which detracts from the simpler usages sketched above.
A note structure might have a property SELF whose value is the
note structure. A note might have a property MY-PIECE whose
value is a set of notes (including itself) comprising the piece
of music in which that note takes part. A note might even have
a property MY-STYLE which was a set of such pieces. Worse yet,
a note might have a property THEORY-FOR-MY-STYLE whose value was
a formal representation as a Lisp program or environment of a
musical theory for the construal or perception of any piece of
music in the style.
The Universe
All musical data in the Lisp Kernel is kept under the
UNIVERSE atom and can be seen on the screen in its entirety by
function GOD. GOD is useful mainly for debugging. There are
separate accessors and updaters for each component of the
UNIVERSE data-structure, defined below. For each property in
the data-structure there will be a GET, ASSIGN, and PUT.
The structure is a hierarchical note-structure of
note-structures (see Figure 4). The values for each main
property of UNIVERSE have the structures shown in Figures 5
through 8. PIECES has names of pieces for properties whose
values are lists of notes (see Figure 5). PIECE-PROPS also has
names of pieces as properties, but their values are
note-structures giving <property value> pairs that are global
with respect to each of the named individual pieces (see Figure
6).
GLOBAL-PROPERTIES has as value a note-structure of <prop
val> pairs that are global for all pieces in the database (see
Figure 7).
The INSTRUMENTS property of UNIVERSE has as its value a
note-structure data structure, each one of whose properties is
a name of an instrument whose values are themselves
note-structures, and so on as shown in Figure 8. The
information under INSTRUMENTS is of course that which is
peculiar to each individual instrument, such as the template
(list of parameters for that instrument), the proper format for
printing each of the instrument's parameters, and default values
for each of the instrument's parameters.
As an example of how this all might work, consider that
some note-structure has been constructed using functions := and
PUT, and assigned to the variable X as its value. (PUT-PIECE X
'BACH) will then append this note-structure to the list of notes
under the piecename BACH; (PUT-PIECE X) will append it to the
list for the default piece ALLNOTES. (GET-PIECE 'BACH)
retrieves the entire list of notes under the piecename BACH.
Composing: Lispfront
Most centers for computer music have evolved a front-end
composing language to help the composer deal with all of the
information needed to do interesting synthesis by software, and
to aid in the composing process itself. At the University of
Washington, a student named Jeff Tinker heard me (several years
ago in the Computer Music Seminar) describe what such a
front-end language ought minimally to be like, then quietly went
away and wrote a compiler in FORTRAN for such a language, which
he called FRONT. Essentially, the FRONT program compiles FRONT
language source code into Music4 "machine language" -- FRONT
outputs a file of data acceptable as input to Music4. FRONT
has been invaluable. Everyone who uses the synthesis software at
the University of Washington tends to become addicted to FRONT
upon exposure.
FRONT allows compositions to be represented as programs. In
one style of programming in FRONT, pieces of music are built up
hierarchically, with NOTE functions at the lowest level.
Modules at any level (motives, chords, phrases, sections...) can
be altered and edited, transposed in time, pitch, or any other
parameter -- played at an altered tempo, loudness, FM index,
etc. It is possible to separate "orchestrational" information
from "note" information, so that whole logical musical blocks
can be slightly re-orchestrated (e.g. tweak the FM index),
independently of the pitches and other qualities specified by a
piano score in common music notation. Notes can also be
generated algorithmically if the composer wishes.
However, the FRONT provided by Jeff Tinker's FORTRAN
compiler program is a skeleton of a language. It can pass
parameters by value or reference at the user's option, do
arithmetic operations, define procedures that call other
procedures, etc., but it has no conditionals, no loops, no
recursion, and no branching. Though one can do an amazing
amount within these limitations, a full language would be an
improvement. The Lisp Kernel requires only some trivial
additions to become a Lisp equivalent of FRONT, called
Lispfront. To the abilities of FRONT are added all the powerful
features of Lisp.
The NOTE procedure in Lispfront writes a note-structure to
the database under the name of the current piece. [4] Figure 10
shows the source code for one possible way of simulating FRONT
in Lisp under the Lisp Kernel. As an example, it defines a
procedure NOTE and uses it to define procedures TUNE and
COUNTERTUNE, which are in turn used to define a procedure
COUNTERPOINT, which when invoked by (COUNTERPOINT), results in
the creation of all the notes of the piece and their storage
under the name of the current piece. Using these NOTE
procedures just as in FRONT, one can duplicate the abilities of
FRONT. This will be useful to musicians who are used to FRONT.
This NOTE procedure is not particularly elegant in Lisp,
since it is a direct translation of the equivalent NOTE
procedure in FRONT. The use of symbol property lists, and
therefore the reliance on side effects, is not only inelegant
but a dangerous way to program in any large system. Other
programming styles for composition, not possible in the original
FRONT, will probably prove more popular with users of Lispfront
and the Lisp Kernel who have become accustomed to Lisp.
The Mixer
The idea of the Mixer part of the Lisp Kernel, which is
still under development, is roughly to duplicate at the note
level the mixing facilities available in a recording studio at
the sample level. This will not be so useful for music
generated by Lispfront, since it is probably easier to edit and
revise the Lispfront source code and regenerate the piece.
Rather, the Mixer is intended for use on musical data captured
via MIDI or other interface to the outside world. The basic idea
is to write procedures that will take a piece of music in the
Lisp Kernel database and use some set of update functions to
alter some sets of selected notes in the piece. The massaged,
captured music can then be output through a general synthesis
program such as Music4P, with enhanced qualities -- perhaps 50
parameters of variablility instead of 5 -- or combined with
other notes generated by Lispfront, and output through Music4P,
MIDI, or any other device. The combination of captured live
music with music composed note by note or using Lispfront might
give interesting musical results.
Playing the Notes
The Lisp Kernel now provides output via MIDI and Music4P.
The procedure MIDI-IN captures MIDI data from a performance;
MIDI-OUT puts any piece in the database out through MIDI. At
the moment, the MIDI procedures use Roger Dannenberg's public
domain software, the Carnegie-Mellon C MIDI Toolkit (Adagio), as
an interface between the Lisp Kernel and MIDI. This enhances
portability (since there are versions of the C MIDI Toolkit for
various microcomputers, and the C source code is available). It
also avoids the garbage-collection problem that plagues attempts
to do real-time input-output in Lisp. However, using the C MIDI
Toolkit means there is a delay of a second or so between the
MIDI-OUT command and the appearance of the sounds in the air. A
version that outputs directly from Lisp would be more dependent
on the machine and the particular implementation of Common Lisp,
but would be truly interactive. It seems worth doing in the
near future.
The procedure PRINT4P prints a piece in the Lisp Kernel
database to a file, in a format acceptable as data for the
Music4P synthesis program. Utilities are provided which should
make it easy to write similar procedures for other synthesis
programs. Figure 9 lists the source code for some of the
relevant procedures. The "template" is an ordered list or
parameters expected by a particular instrument. If the note
contains parameters not in the template, they are discarded from
the copy passed to the printer. If there are parameters in the
template that are missing from the note-structure in the
database, default values are supplied for them. If the user has
failed to specify a default for a particular parameter for a
particular instrument, the program supplies back-up defaults.
The idea behind all this is that no matter what the
information in the note-structure, and no matter what the
expectations of the instrument being played, something
reasonable will result and the note will sound. This allows,
for example, captured MIDI data to be played without alteration
by a Music4P "cello" instrument (which takes over 40
parameters), or complex "cello" music generated by Lispfront to
be heard in simplified form immediately, via MIDI.
Lisp Kernel OOP
In order to enhance portability, and also because it was
fun, I wrote up a simple object-oriented-programming utility for
the Lisp Kernel. All the information for its class definitions
is kept under the atom CLASS-DEFINITIONS in a way similar to the
way the musical information is kept under UNIVERSE (see Figure
11). The CLASS-DEFINITIONS atom has a note-structure as plist
with properties NAME1 NAME2... where these are names of
classes. Each such NAME has a note-structure as its value, with
properties SUPERNODES, whose value is list of immediate
superclasses of the class NAME in local precedence order, and
METHODS, whose value is itself a note-structure with properties
METHOD-NAME1, METHOD-NAME2..., each of whose values is the
quoted lambda-form that is the method of name METHOD-NAME.
There is also provision for an experimental non-hierarchical
dynamic object-oriented-programming technique I call
"applicative assemblage," which I will not attempt to describe
here.
The way to make an instance of a class is with the
procedure MAKE-A. For example, (MAKE-A 'PEANUT) will return as
its value an instance of the class PEANUT. If there is no such
class, it will create one using NEW-CLASS, and then return the
instance; so MAKE-A is also a way to define a class. Each class
has its own internal structure for its instances, which the user
can define. By default, it is simply a note-structure
implementation with two cells, a TYPE-OF cell (accessed by
function TYPE-OF), and a value cell. The value cell can be
filled with the "stuff" function >> and accesssed with the
"yank" function <<. Procedure MAKE-A also takes a value for the
value cell of the instance as an optional second argument. Thus,
(MAKE-A 'PEANUT 'GREEN) returns (TYPE-OF (PEANUT) VALUE
(GREEN)); the TYPE-OF function (TYPE-OF '(TYPE-OF (PEANUT) VALUE
(GREEN)) ) returns PEANUT; the yank function (<< '(TYPE-OF
(PEANUT) VALUE (GREEN))) returns GREEN; and the stuff function
(>> '(TYPE-OF (PEANUT) VALUE (GREEN)) 'RED) returns (TYPE-OF
(PEANUT) VALUE (RED)).
The << and >> functions are written so as to work also if
the object is not an instance of a class, but merely a regular
Lisp object. In that case they revert to the Lisp value of the
object, and to SETQ.
In the current simple version of Lisp Kernel OOP, only
methods are inherited, the inheritance is single (not multiple),
and only the first supernode on the local precedence list of
immediate supernodes is looked at by the FIND-METHOD procedure.
I have left open the option of extending Lisp Kernel OOP in the
direction of CLOS, the Common Lisp Object System. Slots could
be typed and inherited, inheritance could be multiple
inheritance, W-form or Z-form supernode searches could be
implemented using local precedence lists of immediate
supernodes, and some sort of Flavor-like system of Before and
After procedures could be added. Simplicity does have its
virtues, however.
The procedure (PUT-SUPERNODE class-name1 classname2) will
append classname2 to the local precedence list of immediate
supernodes of classname1; ASSIGN-SUPERNODE will replace the old
list with a list of the new value alone. Inheritance is simple
local shadowing. The syntax is (SEND instance-object
method-name &rest arguments).
In an attempt to integrate OOP with POP (procedure-oriented
programming), if no superclass in the class hierarchy has the
named method, the FIND-METHOD function attempts to find the
method as a function named by means of DEFUN in the general
global environment. If defined, it will apply to the arguments
just as the method would have. Therfore, since >> and << do not
care if their object is an instance of a Lisp Kernel OOP class
or a normal Lisp object, it is possible to use SEND on normal
Lisp objects as well as on objects created as instances of
classes, making for a unified syntax if desired. Moreover, the
general global function environment serves as a supremum, that
is, a top supernode, for the partial order which is the graph
of the class hierarchy.
The FIND-METHOD procedure invoked by SEND makes a list of
supernodes visited during the search for a method, checks for
cycles in the supernode graph, and if there is a cycle, aborts
and returns an error message incuding the list of supernodes
visited so that the user can debug the class hierarchy.
Otherwise, there is no constraint on the topology of the graph
of class relations. Figure 12 gives the source code for SEND
and its subprocedures.
IDEAS FOR THE FUTURE
Ports to Other Dialects
It should be possible to port the Lisp Kernel to Xlisp 2.0,
Scheme, and T. A load band of functions and macros for each
should suffice. Considerations for portability include avoiding
the use of Common Lisp's SETF and all Common Lisp fancy
functions and special forms that cannot be readily written in
terms of Minimal Lisp. Even symbol property lists are not
universal: they are found in Xlisp and Common Lisp, but not in
Scheme. The Lisp Kernel does avoid SETF, and is migrating away
from symbol property lists, though it is possible to write the
symbol property list accessors, constructors, and updaters in
Minimal Lisp and therefore in Scheme. The Scheme DEFINE can be
written as a macro in Common Lisp, and the Common Lisp DEFUN as
a macro in Scheme. Without such macros, not only does the
surface syntax differ, but the ways of passing functions as
values and as arguments also differ. For example, a Scheme-ish
DEFINE written in Common Lisp would have to create a lexical
closure and assign it as the value in the variable-value cell of
the name of the function.
Lisp Kernel OOP Extensions
Though it is possible to extend Lisp Kernel OOP in the
direction of the Common Lisp Object System, a more likely
direction for immediate attention is the "applicative
assemblage" experimental form of dynamic, non-hierarchical
object-oriented programming mentioned above. However, some form
of conventional multiple inheritance is attractive. As of this
writing, on October 1988, the software is so new that no one has
had an opportunity to use it much in musical composition.
Extensions to Lisp Kernel OOP will probably be motivated by its
use in alternative, object-oriented forms of Lispfront.
Music Notation Interfaces
Given a stable hardware platform -- that is, a workstation
configuration that seems likely to be widespread, and to be
popular for some years into the future -- it would be worthwhile
writing some interface to some existing music publishing
software, if the hooks into the internal data representation of
that software are available. Or, it would be possible to
implement the VISUAL-REPRESENTATION property of the
note-structure directly within the Lisp Kernel so that Lisp
Kernel notes can be printed to the screen or paper directly, or
taken from a music notation editor on the screen. This would
again be rather machine-dependent, though some isolation is
conceivable, say through a Display PostScript interface. My own
interests lie in other areas, so if such a common-music-notation
interface to the Lisp Kernel is written, it will probably be by
someone else.
NeXT Lispmusic
I have sketched out a digital sound synthesis program in
Lisp. It would be nice to be able to hear one's pieces from
within the Lisp Kernel, interactively. Even with compilation,
however, such a program would be rather slow, when my interests
in sound synthesis demand very fast execution of very large
programs. [5]
In the near future, I will be porting the Lisp Kernel to
one of the first NeXT machines, which was kindly given to me by
the University of Washington for such research. The NeXT
platform is a harbinger of the future in several ways. It is
the first personal computer to come with Common Lisp and 16-bit
digital-to-analog converters as standard equipment. It has
sufficient RAM (8 megabytes) and a fast enough processor to make
these usable. The NeXT also includes a Motorola 56001 digital
signal processing chip. Though the 560001 is, at 10 MIPS or
about 1 MFLOPS, about two orders of magnitude behind the state
of the art in DSP -- a 100 MFLOPS DSP chip has been announced --
it was state-of-the-art when the NeXT was planned. [6]
David Jaffe, of NeXT and formerly of Stanford University's
Center for Computer Research in Music and Acoustics, was
employed by NeXT to write "Music Kit" software. What he did,
apparently, was to implement the "unit generators" of a general
sound synthesis program on the M560001. These are said to be
called from routines written in Objective C, the standard NeXT
machine language. The Objective C routines are said to be
callable, in turn, from within the Franz Allegro Common Lisp
supplied with the NeXT machine. I will be investigating
whether, and in what ways, it will be possible to implement a
Lispmusic using these M56001 and Music Kit routines, within the
Lisp Kernel.
Though the DSP M56001 is rather slow, it is possible to
"lay down tracks" in mass storage and combine these soundfiles
later, so any large and complex piece can be synthesized in
stages or in "parts." Of greater interest in the long term is
the strategy of calling DSP code from within Lisp. The 100
MFLOPS "Vaporware Board" described in my earlier article, [7] or
its 400 MFLOPS big brother, could be plugged into one of the
three empty 32-bit slots on the NeXT machine, or on a similar
computer by some other manufacturer. The 25 MHz Nubus of the
NeXT should be fast enough, even without dedicated DMA channels,
to obtain real-time software synthesis under this regime. In the
worst case, the amount of control information passed to the DSP
board might equal the amount of digital sound information passed
back from it -- around 200 Kbytes/second -- for a total of
around 400 Kbytes/second. This seems really a rather modest
bandwidth requirement in the context of something like a 25 MHz
32-bit bus. (Of course, these are famous last words.)
Artificial Intelligence and the Lisp Kernel
Just as the Lispmusic sketched above would enhance the
output capabilities of the Lisp Kernel, its front-end
capabilities could be enhanced by the addition of various
artificial intelligence software. The interfaces should not be
problematic so long as the AI software is also written in a
lexically scoped Lisp.
For example, Brad Garton's Elthar program, in Common Lisp,
is designed to simulate the environment of a digital recording
studio, where several technicians interact with the composer,
and a group expertise and group history evolve that is specific
to the individual composer. [8] At the moment, neither
Professor Garton nor I see any obstacle in principle to
interfacing Elthar with the Lisp Kernel.
The "Key-Music" system described by A Camurri is also in
Common Lisp. [9] This system combines semantic inheritance
networks, a frame-based knowledge base, Petri nets, and actor
nets. The system is so versatile that it serves equally well
for robotics and for music. Again, there seems to be no obstacle
in principle to interfacing Key-Music as a front end to the Lisp
Kernel. It would enable the creation of style-specific
intelligent composition systems to aid the composer.
I have long had in mind an application of artificial
intelligence to help the composer play his piece through a
general synthesis program such as Music4P (or perhaps some
future Lispmusic). Complexly controlled complex sounds take a
lot of control information. For each software instrument, an
"instrumentalist" program would take high-level instructions
from the composer or score, and turn these into the lower-level
control settings of the perhaps 50 to 100 parameters of the
instrument for each note, much as a human instrumentalist
interprets a score. Such instrumentalist software could even be
individualized -- this oboe player rather than that one.
These three AI programs would then constitute a front-end
expert system (Key-Music), a set of instrumentalists, and a
digital recording studio (including personnel!) for
post-processing, all communicating through the Lisp Kernel.
Conclusion
As systems and software for musical creation become more
complex, the more acute becomes the need for all these programs
to communicate with each other, to cooperate with each other to
work together. The Lisp Kernel is one attempt to pursue such a
strategy. It is worth repeating that the Lisp Kernel by no
means aspires to set up any kind of standard representation -any representation or implementation will do, if it is
suffiently flexible, broad-minded, freely accessible outside the
program that uses it, and extensible or modifiable by the user.
We can hope that other software will be written to integrate
systems together, and that whatever new specialized software
programs arise, they also will be written with an eye towards
cooperating with other programs. A program that can talk to
other programs has its usefulness greatly increased, and is a
better citizen of the software community.
Notes
1. Rahn, John. 1988. Computer Music: A View from Seattle.
Computer Music Journal 12/3 (Fall 1988): 15-29.
2. Steele, Guy. 1984. Common Lisp: The Language.
Press.
Np: Digital
3. See for example the functional implementation of the CONS
cell in Abelson and Sussman, The Structure and Interpretation of
Computer Programs (MIT Press, 1987), pages 81-84 and 207.
4. The name of the current piece is taken from the CURRENT-PIECE
property under the GLOBAL-PROPERTIES property of UNIVERSE, whose
value set by the procedure PIECE, as in (PIECE 'BACH). The
current piece would then remain BACH until the PIECE procedure
is again invoked, with a different name.
5. See note 1.
6. Gunn, Lisa. 1988. At 100 MFLOPS, the Fastest DSP Chip Ever!
Electronic Design 36/23 (October 13, 1988): 73-82.
7. See note 1.
8. Garton, Brad. 1989. The Elthar Program. Perspectives of New
Music 27/1 (Winter 1989), forthcoming.
9. Camurri, A. and Zaccaria, R. An Experimental Approach to
Hybrid Representation of Musical Knowledge. Proceedings of the
First International Workshop on Artificial Intelligence and
Music, St. Augustin, West Germany, September 15-16, 1988
(forthcoming). See also A. Camurri, M. Giocomini, A. Ponasi,
and R. Zaccaria. Key-Music: An Expert System Environment for
Music Composition. Proceedings of the 14th International
Computer Music Conference, Cologne, September 19-25, 1988.
Figure 3
constructors, updaters, accessors
usage
ASSIGN family example
(setq note (:= note 'pitch 7.07))
( ... pitch (7.07) ...)
PUTPV family example
(putpv note 'pitch 8.03)
( ... pitch (7.07 8.03) ...)
GETV family example
(getv note 'pitch)
(7.07)
source code listing
; GETV is the basic accessor
(defun getv (note prop)
(cadr (hasp note prop) ) )
; gets value of prop in note-structure
; HASP returns the tail of the list, starting with PROP,
; IFF some property is PROP, else nil.
(defun hasp (note prop)
; like member, returns list from prop
(and note
; but looks only at odd-numbered
elements
(cond ((eq prop (car note)) note)
(t (hasp (cddr note) prop))
)
)
)
; Function := assigns new val to prop for note, returns new notestructure
; with no side-effects.
(defun := (note prop val)
(defun assign-aux (note prop val result)
(cond ((null note) (reverse result))
(T (let ((noteprop (car note))
(noteval (cadr note)))
(cond ((equal noteprop prop)
(setq noteval (list val))
))
(assign-aux (cddr note)
prop
val
(cons noteval (cons noteprop result))
)
)
)
)
)
(cond ((not (hasp note prop))
(append note
(cons prop (list (list val))))
)
(T (assign-aux note prop val nil))
)
)
; Function putpv appends new val to end of value list for prop for note
; and returns new note-structure (no side-effects).
(defun putpv (note prop val)
(defun putpv-aux (note prop val result)
(cond ((null note) (reverse result))
(T (let ((noteprop (car note))
(noteval (cadr note)))
(cond ((equal noteprop prop)
(setq noteval (append noteval (list val))) ))
(putpv-aux (cddr note)
prop
val
(cons noteval (cons noteprop result))
)
)
)
)
)
(cond ((not (hasp note prop))
(append note
(cons prop (list (list val))))
)
(T (putpv-aux note prop val nil))
)
)
Figure 4
UNIVERSE
(PIECES (val)
PIECE-PROPS (val)
GLOBAL-PROPERTIES (val)
INSTRUMENTS (val))
Figure 5
PIECES
(ALLNOTES ((note1) (note2) ... )
PIECE-NAME1 ((note1) (note2) ... )
.
.
.
)
Figure 6
PIECE-PROPS
(ALLNOTES (insmap (insmap-function)
tempo (72) ... )
PIECE-NAME1 (prop1 (val)
prop2 (val) ... )
.
.
.
)
Figure 7
GLOBAL-PROPERTIES
(current-piece (bach)
tempo (60)
sampling-rate (30000)
number-of-channels (2) ...
.
.
.
)
Figure 8
INSTRUMENTS
(insname1 (template ((start dur amp pitch fmi1 fmi2))
format-properties (default-format ("~6.2F")
pitch ( ) ...
)
default-values
(pitch (8.00) dur (1) stereo (.5) ... )
)
insname2 (template ( ... )
format-properties (default-format ( ) ...
default-values ( ... )
)
.
.
.
)
Figure 9
; 4PFILTER re-orders <par val> pairs to fit template,
; discards pars not in template, and
; supplies default values for pars in template that are not in note.
; To be used in PRINT-ICARD function in
; PRINT4P function (see below) for Music4P output.
; The utilities could also be used for formatted printing of an input
file
; to some other synthesis program, such as Cmix.
(defun 4pfilter (note)
(filter-template note
(car (get-template (get-insname note)))
)
)
; FILTER-GETV looks for defaults if no value found
(defun filter-getv (note prop)
(or (getv note prop)
(get-default-value (get-insname note) prop)
)
)
; FILTER-TEMPLATE does all the work for 4PFILTER
(defun filter-template (note &optional template)
(defun filter-template-aux (note template result)
(if template (filter-template-aux note
(cdr template)
(cons (filter-getv note
(car
template))
(cons (car template)
result
)
)
)
(reverse result)
)
)
(if (not template) (setq template (car (get-template 'defaultinsname))))
(filter-template-aux note template nil)
)
Figure 10
Lispfront example
; Lispfront recreates the abilities of FRONT in Lisp,
; with all the power of Lisp available to massage things.
; The equivalent of D USE NOTE(...) is (NOTE ...)
; The equivalent of D MOD THING=0 is (SETQ THING 0) or (SETF THING 0)
; The equivalent of D DEF TUNE ... D END TUNE is (DEFUN TUNE...)
; There will be many different styles of creating notes and pieces
; with Lisp in the Kernel. What follows is one way that closely emulates
; the FRONT language (compiler written in FORTRAN by Jeff Tinker).
; Basically the idea is to represent each module of a piece, and each
piece,
; as a program, the execution of which causes the notes to be
; stored in the Lisp Kernel database, whence they can be played
; using MIDI, Music4P, etc.
;
;
;
;
SAVE, CREATE, and -> use the property list of the atom NOTE
as a scratchpad. There are other and probably better
ways to write such procedures
that avoid any use of property lists or side-effects.
; SAVE is one version of a function that saves a note to the current
piece.
; All such functions wil use the basic PUT-PIECE function (q.v.).
; The default piece-name is always ALLNOTES.
(defun save (&optional name)
(put-piece (getpl 'note)
(car (get-global 'current-piece)))
)
; CREATE is logically somewhat redundant since the note-structure
updaters
; are also constructors.
(defun create ()
; clears 'note plist and puts basic parameters
(putpl 'note ())
(assign 'note 'instrument ())
; could assign defaults here
(assign 'note 'start ())
(assign 'note 'dur())
(assign 'note 'amp ())
(assign 'note 'pitch ())
(getpl 'note))
; returns property list of 'note
; could have used macro -> below
; Initialize global variables for sample NOTE function.
(setq start* 0.)
(setq durfac* .67)
(setq insno-offset* 0.)
(setq amplev* 1.)
(setq pitlev* 0.)
; Macro -> is a user-interface syntactic sugar procedure
; for easily assigning values to parameters in a note definition.
(defmacro -> (par val)
`(assign 'note ',par ,val))
; Sample NOTE function in FRONT style is
; programmed so that program modules which call NOTE to make notes can
; tranposed in pitch or time, played at a faster or slower tempo,
; played more loudly or softly, and have their instrument numbers
; transposed by an offset.
(defun note (instrument start dur amp pitch)
(create)
(-> instrument (+ instrument insno-offset*) )
(-> start (* durfac* (+ start start*)) )
(-> dur (* dur durfac*) )
(-> amp (* amp amplev*) )
(-> pitch (transpose pitch pitlev*) )
(save)
)
; Examples using the function NOTE
; Define counterpoint of the opening two measures of
; the Prelude in A minor from Book II of J. S. Bach's WTK.
; TUNE is treble line, COUNTERTUNE is bass line.
; Set current piece-name for output to 'Bach.
(piece 'Bach)
(defun tune ()
(note 2 .25
(note 2 .50
(note 2 .75
(note 2 1.
(note 2 1.5
(note 2 2.0
(note 2 2.5
(note 2 3.0
(note 2 3.5
(note 2 4.0
(note 2 4.5
(note 2 5.0
.25
.25
.25
.5
.5
.5
.5
.5
.5
.5
.5
.5
100
100
100
100
100
100
100
100
100
100
100
100
9.00)
9.02)
9.04)
9.05)
9.00)
8.11)
9.03)
9.04)
8.10)
8.09)
9.01)
9.02)
(note
(note
(note
(note
2
2
2
2
5.5
6.5
7.5
8.0
1.
1.
.5
.5
100
100
100
100
8.08)
8.09)
8.11)
8.04)
)
(defun countertune ()
(note 1 1 1 100 7.09)
(note 1 2 1 100 7.08)
(note 1 3 1 100 7.07)
(note 1 4 1 100 7.06)
(note 1 5 1 100 7.05)
(note 1 6 1 100 7.04)
(note 1 7 1.25 100 7.02)
)
(defun counterpoint ()
(note 1 0 1 120 6.09)
; initial low A
(countertune)
(setq amplev* 1.2)
; tune is just a little louder
(tune)
(setq start* 8 amplev* 1. pitlev* 12) ; next measure. one octave
higher
(countertune)
(setq amplev* 1.2 pitlev* -24)
; tune in bass two octaves lower
(tune)
(setq amplev* 1.
; re-initialize global variables
start* 0.
pitlev* 0.)
)
; Simply evaluate the procedure to create notes in counterpoint.
(counterpoint)
Figure 11
Lisp Kernel OOP
; DATA SUMMARY:
;
; 'CLASS-DEFINITIONS
;
(class-name1
;
(SUPERNODES (namex namey...) ; local precedence order
;
METHODS (method-name1 (lambda-form) method-name2 (lambda-form)
... )
;
INITIAL-STRUCTURE (initial-structure)
;
AA-EXEC-CODE (axname1 (lambda-form) axname2 (lambda-form) ... )
;
AA-NEXTNODE-CODE (anname1 (lambda-form) anname2 (lamda-form) ...
)
;
;
;
;
;
;
;
;
;
;
;
;
.
.
.
)
class-name2
(etc.
)
.
.
.
)
Figure 12
Lisp Kernel OOP, SEND procedure
; SEND uses FIND-METHOD to send an object a method that can be inherited
; from the object's supernodes.
;
;
;
;
;
;
;
FIND-METHOD recurses with (get-supernodes object-class method-name)
looking for a non-nil value for (get-method object-class method-name)
which if found it will return,
until parameter "object-class" (from get-supernodes) is nil -that is, if the method is found in none of the object's supernodes -then returns method-name itself (e.g. FM)
which SEND can apply to args if defined in general environment by DEFUN
(defun send (object method-name &rest args)
(apply (find-method object method-name)
(cons (<< object) args))
; makes value of object first
argument
)
(defun find-method (obj meth)
(let ((path nil))
(find-method-aux (type-of obj) meth path)
)
)
(defun find-method-aux (object-class method-name path)
(cond (object-class
; if obj has a class look for methods
(setq path (check-for-cycle object-class path))
(cond (path
(cond ((get-method object-class method-name))
(T (find-method-aux (car (get-supernodes objectclass))
method-name
path)
)
)
)
(T (terpri) (princ "execution terminated -- bad path")
nil)
)
)
(T (symbol-function method-name))
; if no method inherited,
take
)
; function from environment
)
; N.b. can replace (car (get-supernodes obj-cl)) with (get-next-node objcl)
; for generality so that next-node might e.g. be next in local
; precedence list of immediate supernodes, or whatever.
(defun make-path (object-class path)
(cons object-class path))
; returns new path
(defun check-for-cycle (object-class path)
(cond ((member object-class path)
(princ "ERROR -- -- node already on path, cycle in graph")
(terpri)
(princ (make-path object-class path))
nil
)
(T (make-path object-class path))))
; Returns new path if ok, nil if not ok.
; Actually, CHECK-FOR-CYCLE tolerates cycles in the graph
; so long as FIND-METHOD-AUX doesn't get into a loop
; -- will go from class peanut to class veggie,
; or veggie to peanut, but won't go
; from peanut to veggie to peanut.
Download