Sound programming design

damiannz edited this page Oct 12, 2012 · 5 revisions

Current situation

At the moment in the code we have an ofMixer that accepts multiple sources, an ofSoundPlayer that just outputs and testApp that just outputs. For illustration I've added an ofToneGenerator that outputs a tone and ofSoundDelay that applies a delay effect. The mixer has as sources an ofSoundPlayer, an ofSoundDelay and a testApp. The ofSoundDelay takes input from the ofToneGenerator.

In pseudocode:

ofMixer mixer;

ofSoundPlayer musicPlayer;
mixer.addOutput(musicPlayer); 

ofToneGenerator toneGenerator;
ofSoundDelay delay;
delay.setInput(toneGenerator);

mixer.addOutput(delay);

mixer.addOutput(testApp);

current

(solid arrowheads show inheritance, light arrowheads show pointers).

Requirements

We have the following requirements:

  1. We want to be able to change the output of the ofSoundPlayer so that it passes through the ofSoundDelay.
  2. We want to achieve (1) by calling a function on the ofSoundPlayer.

In pseudocode, we want to be able to do this:

musicPlayer.sendGeneratedAudioTo(delay);

and have the musicPlayer send its audio to the delay rather than to the mixer.

Note in the diagram above, the ofSoundPlayer does not have a pointer to the ofMixer, therefore we can immediately say that (2) is impossible. As was pointed out here, to allow (2) we need a doubly linked structure: the ofMixer needs a pointer to the ofSoundPlayer and the ofSoundPlayer needs a pointer to the ofMixer.

What about (1)? Since the ofSoundDelay only has a single source pointer, if we tell it to take audio from the ofSoundPlayer we lose the input from the ofToneGenerator, which is probably not what we want to do. So the ofSoundDelay should also be able to keep a list of inputs and mix them down ... which looks like duplication of the code in ofMixer.

Proposal

Here's my proposal for dealing with both of these problems:

proposal-classes

We have added a base class ofSoundSink which holds an array of pointers to sound sources. (A class should subclass ofSoundSink when it intends to get sound data from another object using audioOut()). To achieve (2) above we added a couple of features to ofBaseSoundOutput so that it now holds an array of pointers back to ofSoundSink instance. We have also renamed ofBaseSoundOutput to ofBaseSoundSource to better match the ofSoundSink name. Now all of the classes that inherit from ofBaseSoundSource get a sinks array, and all of the classes that inherit from ofBaseSoundSink get a sources array.

With this structure, here's what our original scenario above looks like, after doing this pseudocode:

ofMixer mixer;

ofSoundPlayer musicPlayer;
mixer.addSource(musicPlayer); 

ofToneGenerator toneGenerator;
ofSoundDelay delay;
delay.addSource(toneGenerator);

mixer.addSource(delay);

mixer.addSource(testApp);

proposal-case1z

The pairs of arrows are marked by = , they represent the doubly-linked structure mentioned above.

And here's what happens after we do (1), ie set the ofSoundPlayer to send its audio to the ofSoundDelay rather than straight to the mixer. Note that the ofSoundDelay now has two inputs, and that this was able to happen transparently since the methods to handle mixing down were already implemented on ofBaseSoundSink. Note also that we are now able to achieve our requirement (2) from above because the ofSoundPlayer inherits from ofBaseSoundSource and thus holds a pointer back to the ofMixer.

Pseudocode:

musicPlayer.setSink(delay);

proposal-case2