KISA 5 - Adding arrays of audio files, looping and autoplay

Your browser does not support HTML 5. You may want to update your browser.

We will add looping and autoplay to KISA in this part of the tutorial. These might be useful in situations where background sounds are desired as long as the user is looking at your page. To simplify looping, we will use arrays for the sound file names and their buttons.

Because of special requirements in this tutorial, a ToggleButton subclass of the CircleButton class was added. The Toggle Button has two states - "true" or "false" with different colors for each state. In addition to a new constructor, there are new methods: getState, setState, and toggleState that are intended to be used by the calling sketch. A fourth method setColor is used internally to set the button's color. The new class appears in lines 38 to 72 of the file CircleButton.pde.

The ToggleButton can be used in two different ways. It can act as a check button if one calls the toggleState method when the button is clicked. It can also be used as a radio button. It is easy to use this way if the radio buttons are put in an array. The simple setRadioButton method in CircleButton.pde is used facilitate this usage. The array name is passed into the method in case there is more than one group of radio buttons. To get the radio button action when one of the buttons is clicked, setRadioButtons sets all of the buttons to false and then the selected button is set true.

 78  void setRadioButton(ToggleButton[] btnArray, int i)  {
 79    int j;
 80    for (j = 0; j < btnArray.length; j++)
 81      btnArray[j].setState(false);
 82    btnArray[i].setState(true);
 83  } // setRadioButton  

To facilitate looping and to make it easy to add additional files, we will create a "files" array and put the music file names into the array. A third file was add to help demonstrate the coding will allow for more by just adding more files to the array. (You can easily test this ability by commenting out the middle file.) In lines 12 to 16, the array is declared by specifying the file names, a new integer variable is added for the total number of files in the array, and another variable is added for the subscript of the current file. (The new file is much longer and could cause loading delays.) The height of the canvas had to be increased to allow room for the third file name.

 12  String[] files = {"http://brinkje.com/KISA_Sounds/groove",
 13                    "http://brinkje.com/KISA_Sounds/jingle",
 14                    "http://brinkje.com/KISA_Sounds/marcus_kellis_theme"};
 15  int numFiles = files.length;
 16  int currentFile = 0;

We need to give the user a way to select a particular song. Corresponding to the array of files, we declare an array of ToggleButtons treated as radio buttons. This gives a visual indication of the selected file. The array is declared, setup, drawn in the following lines

 24  ToggleButton[] fileBtn = new ToggleButton[numFiles];
...
 54    y = 245;
 55    for (i = 0; i < numFiles; i++) {
 56      fileBtn[i] = new ToggleButton(20, y, files[i], false);
 57      y += 25;
 58    }
 59    fileBtn[currentFile].setState(true);
...
 93    for (i = 0; i < numFiles; i++) {
 94      fileBtn[i].draw();
 95    }

Code is added to the mouseClicked method to process those buttons

184    } else {
185      for (i = 0; i < numFiles; i++) {
186        if (fileBtn[i].isOver()) {
187          setRadioButton(fileBtn, i);
188          currentFile = i;
189          setSource(files[currentFile]);
190          break;
191        }
192      }
193    }

There are two possible interpretations of looping. Possibly only the current files should repeat. In fact, the HTML5 specifies a "loop" attribute that works that way. KISA 5 will use an alternate meaning. KISA5 loops though all the available files using them like a play list. This is achieved in the ended method which is called when "ended" event occurs and the loop option (as indicated by the variable repeatLoop) has been selected. The currentFile variable is incremented (mod numFiles). The new file is set as the source and then played. If only the current file is to be looped, one would delete line 142 where current file is changed. Aternatively, one could use the HTML5 loop attribute. Notice that setRadioButton is called in order to keep the buttons in sync with the file that is being played.

138  void ended() {
139    int j;
140    // Ending event processing
141    if (repeatLoop) {
142      currentFile = (currentFile+1) % numFiles;
143      setSource(files[currentFile]);
144      setRadioButton(fileBtn, currentFile);
145      audio.play();
146    } else {
147      audioStatus = "Finished playing";
148    }
149  }  // ended

When the autoplay attribute is true, the source file will be played when sketch is loaded, when a new source is specified, or when the autoplay attribute is set true. Unfortunately, you will have to modify the source code to show what happens when autoplay is true when the file is loaded but you can easily check out the other conditions.

The loop and autoplay buttons are declared in line 25 as toggle buttons which are used as checkboxes. They are setup in lines 52 and 53 and processed in lines 178 to 184 in response to a mouse click.

 25  ToggleButton loopBtn, autoplayBtn;
...
 52    loopBtn = new ToggleButton(165, 220, "Loop", false);
 53    autoplayBtn= new ToggleButton(270, 220, "Autoplay", false);
...
178    } else if (loopBtn.isOver()) {
179      loopBtn.toggleState();
180      repeatLoop = loopBtn.getState();
181    } else if (autoplayBtn.isOver()) {
182      autoplayBtn.toggleState();
183      audio.autoplay = autoplayBtn.getState();
184    } else {

The KISA6 tutorial will look at how to play multiple files at same time. This capability would be useful in many games, for example.

If you would prefer to use HTML form buttons, checkboxes and radio buttons instead of Processing.js defined buttons, you may want to look at the optional part 5F.

< Previous (KISA 4)     5F (Optional)     Next (KISA 6) >

[+/-] The KISA5.html file

[+/-] The KISA5.pde file

  1  // KISA
  2  // Keep It Simple Audio version 5
  3  // James Brink  brinkje@plu.edu
  4  // 3/9/12   4/12/12 1/29/13
  5  // The constants can be modified.
  6  int width = 500;  // Width of canvas
  7  int height = 310; // Height of canvas
  8  // It is assumed that both .ogg and .mp3 versions of the files are available
  9  // in the same folder.  The file names must be complete URLs except for the
 10  // ending .ogg or .mp3 which must be omitted. It will be added as needed for
 11  // the browser.  Use the prefix "file:///" or "http:// as appropriate.
 12  String[] files = {"http://brinkje.com/KISA_Sounds/groove",
 13                    "http://brinkje.com/KISA_Sounds/jingle",
 14                    "http://brinkje.com/KISA_Sounds/marcus_kellis_theme"};
 15  int numFiles = files.length;
 16  int currentFile = 0;
 17  
 18  // Global variables
 19  Audio audio = new Audio();
 20  int time;
 21  String audioStatus;
 22  CircleButton playBtn, pauseBtn, stopBtn;
 23  CircleButton volumeDownBtn, volumeUpBtn, timeDownBtn, timeUpBtn;
 24  ToggleButton[] fileBtn = new ToggleButton[numFiles];
 25  ToggleButton loopBtn, autoplayBtn;
 26  boolean repeatLoop = false;  // Should the player loop?
 27  
 28  void setup() {
 29    int i, y;
 30    // Setup the sketch
 31    size(width, height);
 32    smooth();
 33    if (audio == null) {
 34      noLoop();
 35      return;
 36    }
 37    frameRate(20);
 38  
 39    audio.addEventListener("loadstart", loadStart, false);
 40    audio.addEventListener("playing", playing, false);
 41    audio.addEventListener("pause", pause, false);
 42    audio.addEventListener("ended", ended, false);
 43    audio.addEventListener("error", error, false);
 44  
 45    playBtn = new CircleButton(130, 145, "Play");
 46    pauseBtn = new CircleButton(230, 145, "Pause");
 47    stopBtn = new CircleButton(330, 145, "Stop");
 48    volumeDownBtn = new CircleButton(165, 170, "Volume down");
 49    volumeUpBtn = new CircleButton(270, 170, "Volume up");
 50    timeDownBtn = new CircleButton(165, 195, "Time down");
 51    timeUpBtn = new CircleButton(270, 195, "Time up");
 52    loopBtn = new ToggleButton(165, 220, "Loop", false);
 53    autoplayBtn= new ToggleButton(270, 220, "Autoplay", false);
 54    y = 245;
 55    for (i = 0; i < numFiles; i++) {
 56      fileBtn[i] = new ToggleButton(20, y, files[i], false);
 57      y += 25;
 58    }
 59    fileBtn[currentFile].setState(true);
 60  
 61  
 62    audioStatus = "";
 63    setSource(files[currentFile]);  // pick the initial audio file  
 64  } // setup
 65  
 66  void draw() {
 67    // Draws the sketch on the canvas
 68    background(#FFFFAA);
 69    fill(#000000);
 70    if (audio == null) {
 71      text("Your browser does not handle the HTML 5 audio tag.  You ", 20, 30);
 72      text("may want to upgrade your browser to the current version.", 20, 60);
 73    return;
 74    }
 75    
 76    textAlign(CENTER);
 77    text("KISA 5", width/2, 30);
 78    text("Source file: " + audio.src, width/2, 60);
 79    text("Status: " + audioStatus, width/2, 80);
 80    text("Current time: " + round(audio.currentTime)
 81         + " sec.   Length: " + round(audio.duration)+ " sec.", width/2, 100);        
 82    text("Volume (0 to 1): " + round(10 * audio.volume)/10.0, width/2, 120);
 83  
 84    playBtn.draw();
 85    pauseBtn.draw();
 86    stopBtn.draw();
 87    volumeDownBtn.draw();
 88    volumeUpBtn.draw();
 89    timeDownBtn.draw();
 90    timeUpBtn.draw();
 91    loopBtn.draw();
 92    autoplayBtn.draw();
 93    for (i = 0; i < numFiles; i++) {
 94      fileBtn[i].draw();
 95    }
 96  }  // draw
 97  
 98  void setSource(String url) {
 99    // Called to set the source file.  Do not include the file extension
100    // in the url.  It will be added here.
101    if (audio.canPlayType && audio.canPlayType("audio/mpeg")) {
102      audio.setAttribute("src", url + ".mp3");
103    } else {
104      audio.setAttribute("src", url + ".ogg");
105    }
106    audioStatus = "File selected";
107  } // setSource
108  
109  void stop() {
110    // Called stop playing the file by pausing it and setting the
111    // time back to 0;
112    audio.pause();
113    audio.currentTime = 0;
114    audioStatus = "Stopped";
115  }  // stop
116  
117  void loadStart() {
118    // LoadStart event processing
119    audioStatus = "Loading";
120  }  // loadStart
121  
122  void playing() {
123    // Playing event processing
124    audioStatus = "Playing";
125  }  // playing
126  
127  void pause() {
128    // Pause event processing.
129    // There is no stop event but a "stop" causes a pause.  This
130    // method checks to see if the current time = 0. If so it assumes
131    // stopped
132    if (audio.currentTime == 0)
133      audioStatus = "Stopped";
134    else
135      audioStatus = "Paused";
136  }  // pause
137  
138  void ended() {
139    int j;
140    // Ending event processing
141    if (repeatLoop) {
142      currentFile = (currentFile+1) % numFiles;
143      setSource(files[currentFile]);
144      setRadioButton(fileBtn, currentFile);
145      audio.play();
146    } else {
147      audioStatus = "Finished playing";
148    }
149  }  // ended
150  
151  void error() {
152    // error event processing
153    audioStatus = "Error";
154  }  // error
155  
156  void mouseClicked() {
157    // Mouse clicked event processing
158    double v, t;
159    int i;
160    if (playBtn.isOver()) {
161      audio.play();
162    } else if (pauseBtn.isOver()) {
163      audio.pause();
164    } else if (stopBtn.isOver()) {
165      stop();
166    } else if (volumeUpBtn.isOver()) {
167      v = audio.volume + 0.1;
168      audio.volume = constrain(v, 0, 1);
169    } else if (volumeDownBtn.isOver()) {
170      v = audio.volume - 0.1;
171      audio.volume = constrain(v, 0, 1);
172    } else if (timeUpBtn.isOver()) {
173      t = audio.currentTime + 0.1 * audio.duration;
174      audio.currentTime = constrain(t, 0, audio.duration);
175    } else if (timeDownBtn.isOver()) {
176      t = audio.currentTime - 0.1 * audio.duration;
177      audio.currentTime = constrain(t, 0, audio.duration);
178    } else if (loopBtn.isOver()) {
179      loopBtn.toggleState();
180      repeatLoop = loopBtn.getState();
181    } else if (autoplayBtn.isOver()) {
182      autoplayBtn.toggleState();
183      audio.autoplay = autoplayBtn.getState();
184    } else {
185      for (i = 0; i < numFiles; i++) {
186        if (fileBtn[i].isOver()) {
187          setRadioButton(fileBtn, i);
188          currentFile = i;
189          setSource(files[currentFile]);
190          break;
191        }
192      }
193    }
194  }  // mouseClicked
195  
196  void setRadioButton(ToggleButton[] btnArray, int i)  {
197    int j;
198    for (j = 0; j < numFiles; j++)
199      btnArray[j].setState(false);
200    btnArray[i].setState(true);
201  } // setRadioButton  

[+/-] The CircleButton.pde file

  1  class CircleButton {
  2    // Used to draw labeled button
  3    int centerX, centerY;
  4    String label;
  5    int radius = 9;
  6    int diameter;
  7    color col = #0000FF;
  8    CircleButton(int centerXX, int centerYY, String labelL) {
  9      // Constructor (centerXX, centerYY) is the center of the circle.
 10      // labelL is the button's label shown on its right.
 11      centerX = centerXX;
 12      centerY = centerYY;
 13      label = labelL
 14      ellipseMode(CENTER);
 15      diameter = 2 * radius;
 16    }
 17  
 18    boolean isOver() {
 19      // Determines the mouse is over the circle.
 20      return sq(mouseX - centerX) + sq(mouseY - centerY) <= sq(radius);
 21    }
 22  
 23    void draw() {
 24      // Draws the circle button with its labelon the right
 25      fill(col);
 26      ellipse(centerX, centerY, diameter, diameter);
 27      fill(0);
 28      textAlign(CORNER);
 29      text(label, centerX + radius + 5, centerY + 5);
 30    }
 31  
 32    void setLabel(String s) {
 33      label = s;
 34    }
 35  
 36  }  // end of CircleButton class
 37  
 38  class ToggleButton extends CircleButton{
 39    boolean state;
 40    color trueCol = #CC0000;
 41    color falseCol = #CCCCCC;
 42  
 43    ToggleButton(int centerXX, int centerYY, String labelL,
 44                boolean isTrue)
 45    {
 46      super(centerXX, centerYY, labelL);
 47      state = isTrue;
 48      setColor();
 49    } // constructor ToggleButton
 50    
 51    void setColor() {
 52      // this method is intended to only be used "internally"
 53      if (state)
 54        col = trueCol;
 55      else
 56        col = falseCol;
 57    } // setColor      
 58  
 59    boolean getState() {
 60      return state;
 61    } // getState
 62  
 63    void setState(boolean isTrue) {
 64      state = isTrue;
 65      setColor();
 66    } // setState
 67    
 68    void toggleState() {
 69      state = !state;
 70      setColor();        
 71    }  // toggleState
 72  } // end of ToggleButton class
 73  
 74  /**
 75   * A global function that allows toggle buttons to 
 76   * be used as radio buttons
 77   */
 78  void setRadioButton(ToggleButton[] btnArray, int i)  {
 79    int j;
 80    for (j = 0; j < btnArray.length; j++)
 81      btnArray[j].setState(false);
 82    btnArray[i].setState(true);
 83  } // setRadioButton  

Updated 11/6/14