next up previous
Next: Adding a level of Up: Composition on top of Previous: Introduction

Architecture of the sound synthesis framework

In this section we describe a framework for a sound synthesis application. We assume that the application has to handle three tasks:

We will run each task in its own thread: synthesis in the synthesis thread, event handling in the event thread, and the application in the main thread [NBPF96].

The synthesis is a hard real-time task. With a sound buffer of 64 samples at a sampling rate of 44100 Hz, the application has less than 1.5 milliseconds to synthesize one buffer of samples. This task should never be delayed since this would introduce clicks. The synthesis thread has therefore the highest priority.

The processing of incoming events is a soft real-time task. The events should be handled as fast as possible. If an event specifies the start of a new sound, the delay should be less than 30 msec. However, it's better to handle the event late (say 50 msec) than not at all.

The synthesis task will in general be part of a larger application. This can be an application for composition - this what we are interested in in this text - but it can be any application that works with sound. This application will in general interact with the user thru a graphical or textual interface. The handling of user input has a lower priority than the synthesis task and the handling of music events. The main thread has therefore the lowest priority of the three threads.

We will call an object that implements a synthesis algorithm a synthesis process. Synthesis is done in buffers, typically 64 to 256 samples long. At any time there can be several synthesis processes alive. The synthesis thread holds an array of pointers to active synthesis processes. It calls each of the processes and passes it a reference to a sample buffer to which the processes add their output. The synthesis thread then writes the buffer to the sound device. A new synthesis process can be added to the array using the add method. A kill message will make an end to the scheduling of a synthesis process and remove it from the array. Synthesis processes can have a life time that varies between very short (in the order of tenths of a second) to very long (the duration of the composition or performance). A particular synthesis process is a subclass of SynthesisProcess. The SynthesisProcess class is an abstract class that defines three methods: birth, live, and die. Subclasses must implement these three methods to define their synthesis algorithm. We will discuss the interactions between the different components in more detail below.

The event thread is a daemon thread that waits for the incoming events. The event thread should block when there are no events available and be resumed when new events come in. When an event arrives indicating a new synthesis process should be created and added to the synthesis thread there must be some way of specifying which synthesis process. The synthesis process itself could be sent over as part of the data of the event. Since events must be handled as fast as possible, this doesn't seem to be the most efficient solution. We adopted a solution that uses either an object factory or prototypes [GHJV95]. The synthesis thread holds an array of synthesis prototypes. Incoming events simply carry an index into the array. The specified prototype is then asked to make a new synthesis process. The event can further carry additional data to initialize the new process.

   figure39
Figure 1: Interaction diagram

Figure 1 shows the interaction between the components in runtime. We adopted the notation used in [GHJV95]. The time flows from top to bottom. The vertical rectangles shows when an object is active. A method call is represented by an arrow from the calling to the called object. The label above the arrow indicates the method name. We have tried to visualise the switching between the three threads: when a vertical rectangle is shaded gray, the thread in which the object is active is suspended. On the right hand side is indicated which of the three threads is active.

Figure 1 shows the addition and killing of a synthesis process by the event thread, followed by another addition issued by the application. When a process is just created it is called once with the message birth. This allows the process to do specific work when it is playing for the first time (a ``fade in'', for example). The process is then said to be alive and receives the live message. A kill event will cause a currently active process to be killed. The process is not immediately eliminated. It gets another chance to finish its song (do a ``fade out'', for example) and will be called once more using the die method. In the last stage the process is removed from the array in the synthesis thread.


next up previous
Next: Adding a level of Up: Composition on top of Previous: Introduction

Peter Hanappe
Thu Jun 18 15:22:17 MET DST 1998