Add support and documentation for AudioSample/SoundFile multi-playback modes
Closed this issue · 1 comments
Hi,
I am trying to avoid clipping when playing multiple times the single SoundFile sample. I am using version 2.4.0-preview (in version 2.3.1 it behaves the same). However, I can NOT control volume of multiple playbacks of soundFile instance. If you repeatedly call .play() you will get multiple laybacks of the sample over each other - however I can only control volume of the first one even if I save the instance to variable. Simple example:
import processing.sound.*;
SoundFile soundfile;
void setup() {
size(640, 360);
background(255);
// Load a soundfile
soundfile = new SoundFile(this, "vibraphon.aiff");
// These methods return useful infos about the file
println("SFSampleRate= " + soundfile.sampleRate() + " Hz");
println("SFSamples= " + soundfile.frames() + " samples");
println("SFDuration= " + soundfile.duration() + " seconds");
// Play the file
soundfile.play();
}
void draw() {
// Map mouseY to volume
float amplitude = map(mouseY, 0, height, 1.0, 0.01);
soundfile.amp(amplitude);
}
void mousePressed() {
soundfile.play();
}
Instance inside ArrayList of Class example:
import processing.sound.*;
SoundFile myfile;
ArrayList<MySound>soundfiles = new ArrayList<MySound>();
class MySound {
SoundFile sample;
MySound(PApplet that, SoundFile thatfile) {
sample = thatfile; //reference to single SoundFile
sample.play();
println("file loaded "+int(random(0, 999)));
}
}
void setup() {
size(640, 360);
background(255);
// Load a soundfile
myfile = new SoundFile(this, dataPath("vibraphon.aiff"));
//add reference to soundfile inside class
soundfiles.add(new MySound(this, myfile) );
}
void draw() {
// Map mouseY to volume
float amplitude = map(mouseY, 0, height, 1.0, 0.01);
for (int i=0; i<soundfiles.size(); i++) {
soundfiles.get(i).sample.amp(amplitude);
}
}
void mousePressed() {
soundfiles.add(new MySound(this, myfile ) );
}
When loading new sample file each time it works but that's not good for performance? Currently we can trigger multiple sounds referencing single file but we can only control the volume of the first instance (this is probably true for other variables such as rate? I did not test that thought)
import processing.sound.*;
ArrayList<MySound>soundfiles = new ArrayList<MySound>();
class MySound {
SoundFile sample;
MySound(PApplet that) {
sample = new SoundFile(that, dataPath("vibraphon.aiff"));
sample.play();
println("file loaded "+int(random(0,999)));
}
}
void setup() {
size(640, 360);
background(255);
// Load a soundfile
soundfiles.add(new MySound(this) );
// Play the file
soundfiles.get(0).sample.play();
}
void draw() {
// Map mouseY to volume
float amplitude = map(mouseY, 0, height, 1.0, 0.01);
for (int i=0; i<soundfiles.size(); i++) {
soundfiles.get(i).sample.amp(amplitude);
}
}
void mousePressed() {
soundfiles.add(new MySound(this) );
}
I would like to see a getter to all instances of the sample being played - like sample.getPlaybacks() - returning array of instances on which I can call amp() pan() rate() stop() play() loop(). Normal behavior of these functions can stay as they are. Alternatively we can have an overloaded function amp(float volume, int instance index) and such - so it can be called without the index in which case the first instance is assumed or with index in which case we try to issue the command to particular instance?
I have also found this function:
[removeFromCache()](https://processing.org/reference/libraries/sound/SoundFile_removeFromCache_.html)
So I might use that to dispose of the soundFile when not needed. I also tried performance test - loading hundreds of files (but the same one file on the disk - just hundreds call to new SoundFile() ) but either my PC is too powerful or there is no penalty for this? What is going on behind the scenes when creating a new SounFile() from file? Does it check if it is already in RAM? In that case I can simply load as many as I like....not sure about this.
I just might need clarification on how to think about this. Thanks a lot.
Relevant source code:
https://github.com/processing/processing-sound/blob/main/src/processing/sound/SoundFile.java
https://github.com/processing/processing-sound/blob/main/src/processing/sound/AudioSample.java
line 308 in AudioSample.java
protected AudioSample getUnusedPlayer() {
// TODO could implement a more intelligent player allocation pool method here to
// limit the total number of playback voices
if (this.isPlaying()) {
// use private constructor which copies the sample as well as all playback
// settings over
return new AudioSample(this);
} else {
return this;
}
}
Will return new copy of AudioSample(this) - but that is not visible from sampleFile I guess?
And line 461
public void play() {
// play() is different from jump() in that, if the current sample is
// already playing back, it creates a new player object to play from
// in chorus
AudioSample source = this.getUnusedPlayer();
source.playInternal();
// for improved handling by the user, could return a reference to
// whichever audiosample object is the actual source (i.e. JSyn
// container) of the newly triggered playback
// return source;
}
So I think we want that reference here?
Hello,
you already found a solution with #92 but just to clarify:
When loading new sample file each time it works but that's not good for performance? Currently we can trigger multiple sounds referencing single file but we can only control the volume of the first instance (this is probably true for other variables such as rate? I did not test that thought)
[...]
I have also found this function:[removeFromCache()](https://processing.org/reference/libraries/sound/SoundFile_removeFromCache_.html)
So I might use that to dispose of the soundFile when not needed. I also tried performance test - loading hundreds of files (but the same one file on the disk - just hundreds call to new SoundFile() ) but either my PC is too powerful or there is no penalty for this? What is going on behind the scenes when creating a new SounFile() from file? Does it check if it is already in RAM? In that case I can simply load as many as I like....not sure about this.
Indeed there is no (decoding or memory) penalty whatsoever for creating new SoundFiles when one has been created from the same file/URL before: unless explicitly removed from cache, a reference to any decoded audio is kept as long as the sketch is running, new SoundFiles of the same audio files get their own playback/volume parameters but share the same audio data underneath. The internal cloning constructor mentioned above (which still only clones the playback settings, not the actual audio data) should probably be made public so that it's easier to create 'copies' of a SoundFile with all of its relevant parameters that might have been changed..