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