Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support and documentation for AudioSample/SoundFile multi-playback modes #91

Closed
trackme518 opened this issue Dec 8, 2023 · 1 comment
Labels
documentation enhancement New feature or request

Comments

@trackme518
Copy link

trackme518 commented Dec 8, 2023

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?

@kevinstadler
Copy link
Collaborator

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..

@kevinstadler kevinstadler changed the title control volume / amplitude of multiple SoundFile instances Add support and documentation for AudioSample/SoundFile multi-playback modes Dec 9, 2023
@kevinstadler kevinstadler added enhancement New feature or request documentation labels Dec 9, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants