playRandomSounds.pde

A Processing sketch which loads random sounds at random intervals from a specific location until no further unplayed sounds are available.

Following on from an earlier post in which I record sounds and list all recordings in a text file, this sketch reads the text file, and plays each of the sounds listed, in random order, and at random intervals.

Why would I want to do this? The destination for these sketches is an installation in which the user is invited to speak. This is recorded and added to the library of sounds. The library of sounds forms part of the installation, and so the user contributes to the unfolding experience - enters the diegesis, and takes part in the story.

You could download playRandomSounds.pde here..


// use the minim library
import ddf.minim.*;
Minim minim;
AudioPlayer player;

// the library file
String audioListFile = "data/rec/fileList.txt";

// an array of file names
String[] listOfFiles;

// the next file to play 
String fileToPlay;

// when to play the next file (seconds)
int timeToPlay;

// an incremental counter for time comparisons
int timeMonitor = 0;

// a boolean which will tell us to close the audioplayer
Boolean endSounds = false;

// on first run, call setUpSound()
void setup(){
	size(512,200);
	minim = new Minim(this);
	listOfFiles = loadStrings(audioListFile);
	setupSound();
}
				

setUpSound() will do most of the graft - choosing a file, picking a time to play it, and keeping track of the list of sounds. This example has decided that the next sound will be played randomly sometime inside the next 10 seconds. In a production version, I might actually fire these sounds some time between the next 20 and 45 seconds, since there needs to be room for other elements of the diegesis to be distinct.

// this function will be called every time I need
// to initialise a new sound to play
void setupSound(){
	int soundCount = listOfFiles.length;
	if (soundCount > 0){
	
	// pick a random sound
		int rIndex = int(random(listOfFiles.length));
		fileToPlay = listOfFiles[rIndex];
		
		// remove chosen sound from array
		// laborious replacement of missing 'slice' functionality???
		String[] tempA = subset(listOfFiles, 0, rIndex);
		String[] tempB = subset(listOfFiles, rIndex+1);
		tempA = splice(tempA, tempB, rIndex);
		listOfFiles = tempA;
		
		// pick a random time from now to play the sound
		// sometime inside the next 10 seconds seems good for now
		timeToPlay = int(random(10));
	} else {
		// there are no more unplayed sounds
		endSounds = true;  
	}
}
				

As I write, a bee is vainly struggling against a windowpane a couple of feet from my left ear. Even this sound is not as irritating as the discovery that Processing has a array 'splice' function, but no array 'slice'. I should perhaps use an ArrayList, rater than an Array, but for now, I invent something called a wheel. In the meantime, if you wanted to have the sounds play indefinitely and so were not averse to repetitions, you could just as well remove the lines which 'slice' the next sound file from the list.

// what to do every frame		
void draw(){
	background(0);
	
	// how long has the app been running?
	int now = int(millis()/1000);
	
	// check that we haven't run out of sounds
	if (!endSounds){
	
		// okay, this check calculates:
		// how long we've been running
		// and if we've reached the next scheduled 
		// trigger to play the next sound
		// timeMonitor accumulates the amount of 
		// time that has passed from sound to sound
		if (now > (timeMonitor + timeToPlay)){
			initSound();
			timeMonitor = timeMonitor + timeToPlay;
			setupSound();
		}
	} else {
		player.close();
	}
}
				

draw() adds nothing to the screen but does decide whether it is time to initialise another sound. I imagine that the vagaries introduced by the time taken to execute lines here and there means that over time, the amount of time the app has been running, and my record of the time that has elpased (i.e. the discrepancy between 'now' and 'timeMonitor') will increase. No matter, this is not life and death.

				
void stop(){
	player.close();
	minim.stop();
	super.stop();  
}
				

initSound simply plays the sound file on demand. As it does so, it pushes the next scheduled 'play-time' further away by the length of the currently playing track.

				
void initSound(){
	player = minim.loadFile(fileToPlay, 2048);
	
	// get available meta data for this sound file
	AudioMetaData meta = player.getMetaData();
	
	// we're interested in the length of the sound (ms)
	int curTrackLength = meta.length();
	
	// this line increases 'timeToPlay' 
	// (and therefore 'timeMonitor' in draw() above)
	// by:
	// the track length (to avoid overlapping sounds)
	// (you may want overlapping sounds - I don't)
	// and '1' (i.e. a second, just to make sure)
	timeToPlay = timeToPlay + int((curTrackLength/1000)) + 1;
	player.play();
}
				

The need to access the metadata of the currently playing file at play.getMetaData() is a consequence of the current inability of AudioPlayer.isPlaying() to detect when a sound has finished. I'm not thrilled at the thought of relying on metadata being available for every sound file. I have no idea if AudioMetaData will always know how long the file in question is. Nevertheless, I don't mind glitches entering into the experience, haunting it with extra, brutal randomness.

Posted by joe

25 May, 2009

processing, code, audio-playback, array-slice.

Comments

What does fileList.txt look like?

Hi Graham fileList.txt is just a plain text file containing a list of relative file paths, each on a newline. j

Add a comment