/********************************************************************************************************* "Ensemble" is the proprietary property of The Regents of the University of California ("The Regents.") Copyright (c) 2005-10 The Regents of the University of California, Davis campus. All Rights Reserved. Redistribution and use in source and binary forms, with or without modification, are permitted by nonprofit, research institutions for research use only, provided the conditions in the included license agreement are met. Refer to the file "ensemble_license.txt" for the license agreement, located in the top level directory of this distribution. **********************************************************************************************************/ /* * Created on Jul 27, 2006 * * TODO To change the template for this generated file go to * Window - Preferences - Java - Code Style - Code Templates */ // // // // // // // // // /** * author Stefan Tomic * written for Ensemble * Audio player supports WAV, AIFF, and SUN (au) audio files * TODO: Needs to support mp3, which can be done with a Java SPI (Service Provider Interface) * Preloading doesn't support specifying play duration (plays the entire duration of the clip) * * Created: 8, August, 2006 * Revision: 25, October, 2007 - supports preloading with "preload" param * Revision: 2/21/2008, S.T. - Now can preload more than one clip. * Each successive clip preloads after the previous one finishes. * Revision: 9/17/2009, S.T. - Implemented simple blocking mechanism (Java 1.6 doesn't auto-block with drain method) * drain and close now called through LineListener's update method * * Copyright (c) 2006 The Regents of the University of California * All Rights Reserved * */ import javax.sound.sampled.*; import java.applet.AppletContext; import java.util.ArrayList; import javax.swing.*; import java.net.*; public class JavaSamplePlayer extends JApplet implements LineListener { private static int DEFAULT_EXTERNAL_BUFFER_SIZE = 50000; private static int RAMPDOWN_TIME_MS = 50; private static boolean DEBUG = false; private String loadWhenDone; private AppletContext browser; private Mixer mixer; private SoundInfo[] soundInfoQueue; private DataLine line; private int currentSound; private boolean preload; private boolean[] clipFinishedPlaying; int nExternalBufferSize = DEFAULT_EXTERNAL_BUFFER_SIZE; int nInternalBufferSize = nExternalBufferSize; private DataLine.Info clipInfo; public void initJavaSamplePlayer(String[] args) throws MalformedURLException { int isnd; int numSounds; System.out.println("Internal Buffer Size: " + nInternalBufferSize); loadWhenDone = args[0]; try { browser = getAppletContext(); if(args.length < 2) { browser.showDocument(new URL(loadWhenDone + "?error=not%20enough%20arguments%20sent%20to%20stimulus%20player%20applet")); } else { currentSound = 0; numSounds = (int) Math.ceil(((double)(args.length-1))/3.0); soundInfoQueue = new SoundInfo[numSounds]; //parsing arguments and initializing soundInfoQueue, an array of //SoundInfo objects that contain playDuration, pause info for each //consecutive sound to be played for(int iarg = 1; iarg < args.length; iarg++) { isnd = (int) Math.floor(((double)(iarg-1))/3.0); switch((iarg-1)%3) { case 0: soundInfoQueue[isnd] = new SoundInfo(args[iarg]); break; case 1: if(args[iarg] != null) soundInfoQueue[isnd].playDuration = (Double.valueOf(args[iarg])).doubleValue(); else soundInfoQueue[isnd].playDuration = 0; break; case 2: if(args[iarg] != null) soundInfoQueue[isnd].pause = (Double.valueOf(args[iarg])).doubleValue(); else soundInfoQueue[isnd].pause = 0; break; } //switch } //for clipFinishedPlaying = new boolean[numSounds]; for(isnd = 0;isnd < numSounds;isnd++) clipFinishedPlaying[isnd] = false; } //else } catch (Exception e) { browser.showDocument(new URL(loadWhenDone + "?error=" + e.toString())); } } //initJavaSamplePlayer() public void play() throws MalformedURLException { try { int nBytesRead; byte[] abData; double silenceBufferSecs; int numSilenceBytes; int silenceBytesLeft; int bytesToRead; int bytesToWrite; int nBytesWritten; long playDurationFrames; long framesRead; int lineFrameSize = 0,soundFrameSize,rampDownFrames; float lineFrameRate = 0, soundFrameRate; boolean rampDown = false; if(!preload) { for(currentSound = 0;currentSound < soundInfoQueue.length;currentSound++) { abData = new byte[nExternalBufferSize]; DataLine.Info info = new DataLine.Info(SourceDataLine.class,soundInfoQueue[currentSound].stream.getFormat(),nInternalBufferSize); line = (SourceDataLine) AudioSystem.getLine(info); //if line can't get a sourceDataLine if(line == null) { browser.showDocument(new URL(loadWhenDone + "?error=poorly%20formed%20sound%20URL")); } line.addLineListener(this); ((SourceDataLine) line).open(soundInfoQueue[currentSound].format,nInternalBufferSize); line.start(); System.out.println("NOT preloading"); lineFrameSize = line.getFormat().getFrameSize(); lineFrameRate = line.getFormat().getFrameRate(); soundFrameSize = soundInfoQueue[currentSound].format.getFrameSize(); soundFrameRate = soundInfoQueue[currentSound].format.getFrameRate(); nBytesRead = 0; //handle pause by sending a stream of 0's to the line if(soundInfoQueue[currentSound].pause > 0) { silenceBufferSecs = soundInfoQueue[currentSound].pause; numSilenceBytes = lineFrameSize*((int) (lineFrameRate*silenceBufferSecs)); silenceBytesLeft = numSilenceBytes; while(silenceBytesLeft > 0) { if(silenceBytesLeft > nExternalBufferSize) bytesToWrite = nExternalBufferSize; else bytesToWrite = silenceBytesLeft; //fill buffer with 0's for(int iByte = 0; iByte < bytesToWrite; iByte++) abData[iByte] = (byte) 0; System.out.println("Writing to DAC"); nBytesWritten = ((SourceDataLine) line).write(abData,0,bytesToWrite); silenceBytesLeft -= nBytesWritten; System.out.println(nBytesWritten + "silence bytes written " + " " + silenceBytesLeft + " Silence bytes left"); } } //if playDuration is positive, then use playDuration to determine the duration of sound to play //otherwise if playDuration is negative (should be -1), this signals the player to play the whole sound file if(soundInfoQueue[currentSound].playDuration > 0) { playDurationFrames = (long) (soundInfoQueue[currentSound].playDuration*soundFrameRate); } else { playDurationFrames = soundInfoQueue[currentSound].stream.getFrameLength(); } framesRead = 0; while (nBytesRead != -1 && framesRead < playDurationFrames) { if((playDurationFrames-framesRead)*soundFrameSize < abData.length) bytesToRead = (int) (playDurationFrames - framesRead)*soundFrameSize; else bytesToRead = abData.length; nBytesRead = soundInfoQueue[currentSound].stream.read(abData, 0, bytesToRead); framesRead += (long) nBytesRead/soundInfoQueue[currentSound].format.getFrameSize(); System.out.println(bytesToRead + "audio file bytes left to read"); System.out.println(nBytesRead + "audio file bytes read"); if (nBytesRead >= 0) { System.out.println("Writing to DAC"); nBytesWritten = ((SourceDataLine) line).write(abData, 0, nBytesRead); System.out.println(nBytesWritten + " audio file bytes written"); System.out.println(); } } soundInfoQueue[currentSound].stream.close(); } //for each sound in queue //pad the end of the buffer with silence //this avoids a click at the end when //suddenly closing the line right when //the sound finishes playing silenceBufferSecs = 0.5; numSilenceBytes = lineFrameSize*((int) (lineFrameRate*silenceBufferSecs)); silenceBytesLeft = numSilenceBytes; while(silenceBytesLeft > 0) { if(silenceBytesLeft > nExternalBufferSize) bytesToWrite = nExternalBufferSize; else bytesToWrite = silenceBytesLeft; abData = new byte[nExternalBufferSize]; //fill buffer with 0's for(int iByte = 0; iByte < bytesToWrite; iByte++) abData[iByte] = (byte) 0; nBytesWritten = ((SourceDataLine) line).write(abData,0,bytesToWrite); silenceBytesLeft -= nBytesWritten; } //load the next page browser.showDocument(new URL(loadWhenDone),"_self"); } //if(!preload) //if preloading, open and start the clip now if(preload) { System.out.println("Preloading"); //assuming all files have the same format. Just obtaining format of first sound DataLine.Info info = new DataLine.Info(Clip.class,soundInfoQueue[0].format); line = (Clip) AudioSystem.getLine(info); line.addLineListener(this); for(currentSound = 0;currentSound < soundInfoQueue.length;currentSound++) { if(line == null) { browser.showDocument(new URL(loadWhenDone + "?error=poorly%20formed%20sound%20URL")); } System.out.println("Opening and starting the clip"); //pause before sound if specified in params if(soundInfoQueue[currentSound].pause > 0) Thread.sleep((int) soundInfoQueue[currentSound].pause*1000); ((Clip) line).open(soundInfoQueue[currentSound].stream); ((Clip) line).start(); //block here until sound finishes playing while(!clipFinishedPlaying[currentSound] ) { Thread.sleep(50); } } //for //load the next page browser.showDocument(new URL(loadWhenDone),"_self"); } } catch (Exception e) { browser.showDocument(new URL(loadWhenDone + "?error=" + e.toString())); } } public void update(LineEvent event) { System.out.println(event.toString() + " " +currentSound + " " + soundInfoQueue.length); //if stop even occurred while in preload mode (sound finished loading), then drain at this time if(event.getType().equals(LineEvent.Type.STOP) && preload) { System.out.println("Received STOP (finished preloading). Received " + ((Clip) line).getMicrosecondLength() + " microseconds of sound"); System.out.println("Received STOP. Now draining the line"); ((Clip) line).drain(); ((Clip) line).close(); } if(event.getType().equals(LineEvent.Type.CLOSE) && preload) { clipFinishedPlaying[currentSound] = true; System.out.println("Setting clipFinishedPlaying to true"); } } //adds element to an ArrayList. If the element is null //adds "0" instead of null private void listAdd(ArrayList list,String element) { if(element == null || element == "") list.add("0"); else list.add(element); } public void init() { ArrayList argList = new ArrayList(); String args[]; String param, preloadParam; boolean parametersToRead = true; int soundCount = 2; preloadParam = getParameter("preload"); if(preloadParam == null || (preloadParam.toLowerCase()).equals("true")) preload = true; else preload = false; listAdd(argList,getParameter("loadWhenDone")); listAdd(argList,getParameter("audioURL1")); if(argList.get(0) == "0" || argList.get(1) == "0") { //report error } listAdd(argList,getParameter("playLength1")); listAdd(argList,getParameter("pause1")); while(parametersToRead) { param = getParameter("audioURL" + (soundCount)); if(param != null) { listAdd(argList,param); listAdd(argList,getParameter("playLength"+(soundCount))); listAdd(argList,getParameter("pause" + (soundCount))); } else { parametersToRead = false; } soundCount++; } args = (String[]) argList.toArray(new String[1]); try { initJavaSamplePlayer(args); play(); } catch (Exception e) { e.printStackTrace(); } } } //class JavaSamplePlayer