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.