A C++ Framework for Generic Programming and Composition
Michael Hamman, Simon Pamment
1814 Larch Place
Urbana, IL 61801
This paper presents current work in the development of a light-weight programming framework for music composition. The framework is designed to make development of compositional applications relatively simple, while still maintaining representational flexibility. This flexibility is achieved through object-oriented design and through use of the C++ Standard Template Library (STL). The framework is based on a rather simple model. At the highest level of abstraction, the model constitutes two basic functions: signal generation and signal rendering. Musically significant computational structures are created by linking, in various ways, signal generators and then rendering the data thus produced as musical signals. The framework is in its early development stages: however, as we continue to develop it, our intention is to incorporate, more and more, generic programming techniques within the STL.
1 Background and Introduction
The computer allows composers to specify task environments with an unprecedented degree of specificity. However, to do so with great power can be tremendously difficult and challenging for a busy, active composer. Off-the- shelf computer music tools allow us to accomplish a great deal in terms of compositional production. Nevertheless, many composers prefer to develop their own tools in order to actively take part in the creation of the specific techniques and representations that populate their composing task environment. For those composers, programming languages provide the most flexible "user interface".
Unfortunately, to even get the basics of sound computation working can be taxing: writing samples to sounds files or to the audio hardware, writing optimized synthesis routines and compositional pattern applications can easily consume all available time one has for composition production and research. While there are a number of programming libraries for sound synthesis and for generating musical patterns, there are few which emphasize the kind of software architectural flexibility that allow composers to investigate experimental notions of musical structure without getting mired in endless implementation difficulties. What is needed is a well-designed methodology for incorporating the elements of those libraries into a larger whole which itself can be easily extendible and replacible.
This paper presents a lightweight programming framework for creation of composition software systems, along with a discussion of some of the software design criteria that underlie its design. The goal of this programming framework is to provide a small but extendible set of interfaces for developing composition and synthesis programs ranging from the production of scores to real-time sound synthesis applications. The design of the framework emphasizes ease of application development without sacrificing representational flexibility. We achieve this by observing effective object-oriented design principles and through use of parameterized types by means of the C++ Standard Template Library (STL).
In this paper, we first discuss basic object-oriented (OO) design issues that are often overlooked in introductory material, including "design patterns" for software, and use of the C++ STL (this section extends remarks made in ). We do this in order to motivate a way of thinking about software frameworks rather than software libraries. Then we discuss the design of our initial programming framework for music composition.
2 Software Design Issues
2.1 Initial Criteria
Our project is predicated on the idea that the computer provides an environment in which composers can develop tools that directly support techniques used in the creation of musical artifacts (musical works, notions of musical structure, compositional procedure, etc.). For such a composer, computer systems are themselves musical artifacts—"things" that are of relevance to musical and acoustical intelligence. In this regard, we understand the composer as a designer of computer systems rather than a user.
2.2 Object-oriented Design
The composer as designer views the process of composition to be just as important as the result produced. We feel that object-oriented design can assist the composer as designer. Of primary importance to object-oriented design are the following criteria:
flexibility, extendibility, and scalability.
This is all the truer within task domains, like music composition, in which the functional requirements at any given time are murky at best. One wants an environment in which changes can be easily made without having to make enormous effort in fixing the resulting bugs that are often introduced.
Object-oriented design (OOD) involves application of a number of basic design principles and thus involves considerable experience in designing and building actual systems. Thankfully, over the years, a number of principles have been abstracted from experience. Two of the fundamental principles of good OOD are low coupling and high cohesion . Low coupling means that objects are not overly dependent on other objects for their ability to function in a variety of situations. Highly coupled objects make for very inflexible software designs since the removal of one object could break the functionality of other objects.
High cohesion means that classes and methods constitute well understood functionalities: they do one thing and do it well. Classes and methods that try to do too many things are difficult to understand and thus become unwieldy in complex applications.
Another major principle of OOD is the "Open-Closed" principle, which says that software entities like classes, packages, and methods, should be open to extension but closed to modification . Existing classes should not require modification in response to changes elsewhere in the system. It should be possible instead to extend already existing entities.
These three principles are supported through encapsulation, polymorphism, and abstraction. Encapsulation means that those properties of a class that are strictly related to how it does things are kept hidden from the outside world. Polymorphism defines a relation among objects in which one object can be interchangeable with another. Polymorphism is exercised through class inheritence and is enabled through the principle of interface inheritance.
An interface defines a set of methods that a class promises to the outside world. Precisely how these methods are executed is an implementation detail and of no concern to the user of the class to which the method belongs. A single interface can be shared among many objects, and a single object can have many interfaces. Interfaces allow for immense flexibility—objects know nothing of other objects; they only know about interfaces. Objects sharing an interface are thus completely interchangeable.
Polymorphism is enabled when the base class in a class hierarchy defines a single abstract interface which each derived class implements in some particular way. With polymorphism, objects are interchangeable at run-time.
The kind of flexibility attainable through good OOD is hard to overestimate; and it can extend across many architectural levels, from the level of the class to the level of larger software components.
2.3 Design Patterns
The criteria of flexibility, extendibility, and scalability are further supported through observation and codification of patterns of relations among entities. These so-called design patterns help software designers "reuse successful designs by basing new designs on prior experience" . A design pattern is the well-documented articulation of a canonical relation among classes of objects, in which an aggregate of objects—and the interrelations their structure defines—is known to solve a recurrent software design problem.
As an example, consider the following situation. In a more naive OO design, two classes—Window and Rectangle—are interrelated in that Window inherits its implementation and interface from Rectangle. The Rectangle might have a single method called draw(), which Window inherits. Good reasoning supports this structure: after all, a window is-a rectangle and is-a is the canonic relationship that defines inheritence. But then what happens if we want to draw a round window? In such a case, the structure would have to change, which could be very costly indeed.
A more flexible design would observe the principle of Delegation—an important principle in design patterns. Following such a design approach, a window is not a kind of rectangle; rather, it has a rectangle. With this design, whenever an object invokes a Window’s draw() method, that call is "delegated" to the Rectangle object, which is now an attribute of Window. The advantage gained here is that it is now easier to compose behaviors at run-time. For instance, at run-time, rather than associating a Window object with a Rectangle, one could associate it with a Circle without having to change any of the code.
Delegation constitutes one of about a dozen principles underlying an ever-growing number of design patterns. Design patterns are coming to increasing use in both research and industrial applications.
2.4 Generic Programming and the STL
Generic programming ups the ante on software design flexibility. Generic programming defines a set of requirements on data types, rather than particular data objects and their operations . As Austern explains it, "[a] generic algorithm is written by abstracting algorithms on specific types and specific data structures so that they apply to arguments whose types are as general as possible" .
C++ supports generic programming through a mechanism called "templates." Using templates, one can create classes that define basic types of classes, rather than specific classes. Collection objects provide a good example. Consider an Array class. With standard object-oriented languages, one would have to provide such an array class for every built-in data type (integers, floats, etc.) as well as user-defined data types, or provide a base class and implementing the Array class through polymorphism. With templates, one can create a generically typed array through the use of templates. Such an array object could then contain any data type.
The standard template library (STL) defines an extensive library of generic algorithms and generic collections on which those algorithms can operate. More than that, STL defines the mechanisms by which collections and algorithms can be linked up with enormous flexibility.
Ultimately, however, the STL was designed to be extensible: "just as the different STL components are interoperable with each other, they are also interoperable with components you write yourself" . This means that, by adding your own algorithms and (less often) your own collections, you can effectively extend the STL toward suitable domain-specific frameworks.
3 Description of the Framework
We have developed a very lightweight object-oriented framework to support creation of composition applications. What is a framework? A framework is "a cohesive set of classes and interfaces that collaborate to provide services for the core, unvarying part of a logical subsystem" . Use of frameworks relies on the so-called "Hollywood Principle": "Don’t call us, we’ll call you." This means that in creating applications based on a framework, you create objects and algorithms which, in some way, extend common objects and algorithms within the framework. The common objects and algorithms are invoked to handle common tasks. When they need to do something that is particular to the current application, they invoke methods within the objects you create. Through effective object-oriented design, this is done seamlessly, with minimal impact on performance.
3.1 The Model
At the highest level of abstraction, the current model constitutes two basic functions: generating signals and rendering them. At a slightly lower level of abstraction, a supplier/consumer pattern is used. This pattern stipulates a relationship between objects in which one requests and then"consumes" data from one or more other objects.
There are three basic types of objects: an algorithm object, a signal generator, and a renderer. A renderer acts as a "consumer": it contains a list of "suppliers" (signal generators) from which it requests data as needed. The renderer takes this data and renders it according to a specific procedure. Currently there is a single renderer: for rendering sound samples to a sound file. Upon completion, the framework will include rendering as MIDI files, and rendering in real-time to the audio hardware. Additionally, composer/ developers can add their own renderers to the class hierarchy.
3.2 The Renderer
A Renderer contains a list of Signal Generators. The iterated flow of control is as follows: (1) The Renderer passes a "frame" to each Signal Generator to fill with data; (2) the requested Signal Generator fills the frame that was passed to it with data according to a specified algorithm; (3) the Renderer composes the resulting frames (usually through summation); (4) the Renderer "renders" the resultant "master" frame which, in the case of sound file rendering, means writing the frame as samples to the sound file.
3.3 SignalGenerators and the Generator Class Hierarchy
SignalGenerators produce data (or "signals") that other SignalGenerators or Renderers use. SignalGenerators have a single public method interface, compute(), which is the method invoked by any consumer that treats it as a "supplier." In the above example, the Renderer invokes the compute() method for each SignalGenerator in its list.
The SignalGenerator class hierarchy (including Renderer) is as shown in figure 2.Generator is an abstract base class: it provides the public interface for all generator classes. The SigGen class includes notions of time that are particular, for instance, to sample generation or to discrete events in score production. The SigGen class is the base class for actual signal generation objects. The framework, when finished, will have a supply of basic signal generators, like sine, etc.
A CompositeSigGen is a signal generator that can contain a list of references to other SigGens, or objects derived from SigGen (including other CompositeSigGen objects). There are currently two CompositeSigGen classes defined: EventGen and Renderer.
An EventGen is populated with a list of particular types of SigGens: "scheduled" signal generators. These are signal generators that maintain some kind of scheduling algorithm and a way of mapping scheduled events to elemental SigGen objects. EventGens can operate at the macro-time level in order to generate discretely perceived events; or, they can operate at the micro-time level in order to generate sonic structures of indeterminate granularity.
3.4 Generic "Frames"
All of the above classes are templated classes: they are based on a generic notion of the "frame." A frame is a collection of elements. Each such element constitutes a single musically significant entity, from a single sample for sound file storage or audio hardware realization, to a higher-level musical event for storage to a score file or dispatch across a network. The particularity of these elements are defined by the composer.
3.5 A Brief Example
The following is a very simple example of use. It constitutes a main() function for a simple program that creates an EventGen object, which takes two SchedSigGens: one is based on a mythical "Foo" generator and the other on a similarly mythical "Bar" generator. Due to space restrictions, some details are left out here that will be made available at the presentation.
*eg = new EventGen ; SchedSig *sg1 = new SchedSig (Foo,F1); SchedSig *sg2 = new SchedSig (Bar,F2); eg->addSupplier(sg1); eg->addSupplier(sg2); Renderer rend("test1"); rend.addSupplier(eg); rend.run(10);
In this brief passage, an EventGen is created and two SchedSigGens are added to its list of SigGens ("suppliers"). A Renderer is created, to which the EventGen object is added. Finally, the Renderer method run() is invoked, causing the rendering of 10 seconds of musical data being written to the output file called "test1."
In this example, the generic type is specialized to some actual type called "F." F is a mythical frame type that contains some number of similarly mythical musically relevant element types. The particularity of the elemental types, of the frame type, of the particular SigGen classes, and the algorithms used in various places are defined within the application.
4 Final Comments
In this paper, we advocated adoption of modern OO design practice in the development of programming frameworks for music composition. Frameworks are software environments which support rapid development of, and experimentation with, specific software applications. We then presented a high level view of the design of one such framework, in which a skeletel set of classes are defined, each of which are extendible.
The framework is a work in progress. Our goals are to finish an initial version and use it in the development of at least one specific composition project. Meanwhile, we will continue to refactor the design in order to incorporate, to a greater degree, aspects of generic programming. Our final goal is to have a framework that is a kind of "Audio" STL which allows composers to develop composition applications, or to further extend it through incorporation of other libraries and composition algorithms, thus defining new frameworks.
 A. Freed, A. Chaudhary: "Music Programming with the new Features of Standard C++, Proceedings of the 1998 International Computer Music Conference. San Francisco: ICMA. 1998. pp. 244 – 247.
 G. Booch: Object-Oriented Analysis and Design with Applications. Redwood City: Benjamin/Cummings, 1994.
 G. Meyer: Object-Oriented Software Construction. Englewood Cliffs: Prentice Hall. 1988.
 E. Gamma, R. Helm, R. Johnson, J. Vlissides: Design Patterns: Elements of Reusable Object-Oriented Software. Reading, MA: Addison-Wesley. 1995.
 M. Austern: Generic Programming and the STL. Reading, MA: Addison-Wesley. 1999.
 ObjectSpace: Advanced Object Design with Patterns, course notes. ObjectSpace, Inc. 1998.