KISA 5 Form - Using HTML buttons


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



Loop   Autoplay

reserved for file buttons and names

Volume: 100%
Current Time: 0.0/1.0 sec
Volume:
Time:

Alternative 1: Getting access to the Processing.js audio object

Alternative 2: Sophisticated onClick commands

The file was updated 2/1/13. Some parts are significantly more sophisticated than earlier versions.

The tutorial assumes that you have already understand KISA parts 1 - 5.

In this lesson we will replace the replace the non-standard looking CircleButtons and ToggleButtons used in the previous KISA sketches with HTML form buttons, checkboxes, and radio buttons. It is fairly easy to use HTML buttons because it is possible for Javascript to call Processing.js methods and Processing.js to call Javascript functions. The connection is done using getElementById function in Processing.js and getInstanceById in Javascript. One limitations is the Javascript cannot directly access Processing.js variables even though it can access its methods.

Using HTML5 does provide one potential advantage. We will show how the <meter> tag and <input type="range">tag can be used.

There are several alternatives. We will discus a couple of them later. But initially, we will use a rather straight forward method even though it uses more code than some of the alternatives. This technique was suggested by "Scott" in http://makesomethingeveryday.scottkobewka.com/draw/draw_code.html which might not be available any more.

Using HTML form buttons means that the .pde and .html files are much more interrelated. Arbitrarily we will begin by looking at the .pde file. Discussion of the proposed HTML5 meter tags and range inputs will be postponed until after completing the discussion of the buttons. .

The .pde file

The most significant change in the .pde file when replacing the Processing.js buttons with HTML buttons is that we will replace the lengthy mouseClicked methods with a number of simple methods that carry out the operations when a particular button is clicked. These new methods are found in lines 134 to 161. All the new method names are prefixed with a "p" to signify they are part of the Processing.js file. (Corresponding Javascript functions are prefixed with "js".)

The code previously in the mouseClicked methods for the "Play", "Pause", and "Stop" buttons was moved to new methods called pPlay, pPause, and pStop. pPlay will be called when the HTML "Play" button is clicked. It just invokes audio.play. Likewise clicking the "Pause" and "Stop" buttons will invoke the pPause and pStop methods. The pStop method just calls the existing stop method. Actually, the stop method could have been called directly by Javascript but using the pStop method fits the pattern set up for the other buttons.

149  void pPlay()  { audio.play(); }
150  void pPause() { audio.pause(); }
151  void pStop()  { stop(); }

The mouseClicked code for the "Volume down" and "Volume up" buttons was moved to the new pVolumeDown and pVolumeUp methods which will be called when corresponding HTML buttons are clicked. The "Time down" and Time up" buttons where handled in same way.

153  void pVolumeDown() {
154    double v = audio.volume - 0.1;
155    audio.volume = constrain(v, 0, 1);
156  }
157  void pVolumeUp() {
158    double v = audio.volume + 0.1;
159    audio.volume = constrain(v, 0, 1);
160  }
...
164  void pTimeDown() {
165    double t = audio.currentTime - 0.1 * audio.duration;
166    audio.currentTime = constrain(t, 0, audio.duration);
167  }
168  void pTimeUp() {
169    double t = audio.currentTime + 0.1 * audio.duration;
170    audio.currentTime = constrain(t, 0, audio.duration);
171  }

The mouseClicked code for the "Loop" and "Autoplay" buttons had to be modified (actually simplified) because the original code used button routines but the buttons have been eliminated. pLoop just complements the repeatLoop boolean variable. As in KISA 5, this version of KISA loops though all the audio files instead of using the audio API attribute loop. The pAutoplay directly compliments the audio API autoplay attribute.

176  void pLoop() { repeatLoop = !repeatLoop; }
177  void pAutoplay() { audio.autoplay = !audio.autoplay; }

When one of the files is selected by clicking one of the new radio buttons, the pChooseFile(int i) method is called. i is the number of the file selected. So it sets the currentFile variable to i and then uses setSource to set the source to that file.

179  void pChooseFile(int i) {
180    currentFile = i;
181    setSource(files[currentFile]);
182  }

There are few questions remaining: How do we make sure that the "Loop" and "Autoplay" checkboxes actually correspond to the corresponding values in the .pde file? This is a nontrivial question because in some browsers, reloading the web page doesn't reset the check boxes to the default state. The same question applies to the file selection radio buttons. Another question is how do we get the names of the files from the .pde file where they are declared into the HTML file. The answers are similar: we take advantage of Processing.js ability to make changes in the .html file using document.getElementById. When we look at the .html file, we will observe that the loop and autoplay buttons are identified by the id's "loopbox" and "autoplayBox". Thus it is easy to set those elements properly. A call to jsFileButtons creates the specified number of buttons for listing the file names. The file buttons have the form "file#" (where the "#" is replaced by a sequential number) while the name of the files are stored in spans with the ids of the form "file#Name". We can use a loop to set the file names and another statement to mark the current file. In the setup function we use the following code to initialize the HTML elements.

 48    document.getElementById('loopBox').checked = audio.loop;
 49    document.getElementById('autoplayBox').checked = audio.autoplay;
 50    jsFileButtons(numFiles);
 51    for (i = 0; i < numFiles; i++)
 52      document.getElementById('file'+ i + 'Name').innerHTML = files[i];
 53    document.getElementById('file'+ currentFile).checked = true;

There is one more place where synchronizing is important. To keep the file selection radio buttons indicating the current source file while looping, a statement in the ended function makes sure that the current file is checked. Recall that the button names have the form file#.

137      document.getElementById('file'+currentFile).checked = true;

The values of the volume, current time, and duration of the audio are displayed in the HTML form. In the draw method, the current values are sent to the HTML display function using the following code

 80    text("Volume (0 to 1): " + round(10 * audio.volume)/10.0, width/2, 120);
 81    text("Loop: " + repeatLoop + "   Autoplay: " + audio.autoplay, width/2, 145);
 82    if (frameCount % 5 == 0) {
 83      try {  // designed to solve a timing problem - drawn before setup finished
 84        setVolumeGraphics(audio.volume);
 85        setCurrentTimeGraphics(audio.currentTime, audio.duration);
 86      } catch (Exception e) {
 87      }
 88    }

Why is the try/catch structure needed? It turns out the draw methods may be called before the HTML code is completed. This would cause an exception. The try/catch structure just ignores the problem until the HTML code is complete. It didn't seem necessary to update the volume and time displays 20 times a second. To reduce the load on the computer, they are updated every 5th time the canvas is redrawn.

There are a couple of other minor changes. Largely for debugging purposes, the values of Loop (repeatLoop) and Autoplay were to the added sketch in line 81. The height of the sketch was reduced in line 7 because the space for the Processing.js buttons was no longer needed.

The .html file

We will now turn our attention to the .html file which obviously becomes more complicated when HTML form buttons are used.

The form

Lets first look at the section where the buttons are added. Remembering "KIS", we will not bother with nice formatting. The form is rather straight forward. In each case, the onclick event calls a js...() function. (We are using the "js" prefix to help make it clear that the function is a Javascript function included in this file.) The code for the buttons is shown below.

 85    <form>
 86       <input type="button" value="Play" onclick="jsPlay()">
 87       <input type="button" value="Pause" onclick="jsPause()">
 88       <input type="button" value="Stop" onclick="jsStop()">
 89       <br>
 90       <input type="button" value="Volume down" onclick="jsVolumeDown()">
 91       <input type="button" value="Volume up" onclick="jsVolumeUp()">
 92       <br>
 93       <input type="button" value="Time down" onclick="jsTimeDown()">
 94       <input type="button" value="Time up" onclick="jsTimeUp()">
 95       <br>
 96       <input id="loopBox" type="checkbox" value="Loop" onclick="jsLoop()">Loop &nbsp;
 97       <input id="autoplayBox" type="checkbox" value="Autoplay" onclick="jsAutoplay()">Autoplay

After these buttons, come the buttons for the file names. The problem is that the number of files and their names is specified in the .pde file. Even worse, we don't even know for sure that the .pde file has been loaded yet. So we just reserve room for these buttons with a section whose contents will be determined later.

 99        <div id="putFileButtonsHere"><br>reserved for file buttons and names<br></div>

The form continues with the experimental section and alternatives which will be discussed later.

Javascript functions

We now turn our attention to the js...() functions that respond to clicking a button. The first eight are all similar so only a few are shown below.

  7    <script type="text/javascript">
  8      
  9     function jsPlay() {
 10        var pjs = Processing.getInstanceById("KISA");
 11        pjs.pPlay();	  
 12  	}
 13     function jsPause() {
 14        var pjs = Processing.getInstanceById("KISA");
 15        pjs.pPause();
 16     }
 17     function jsStop() {
 18        var pjs = Processing.getInstanceById("KISA");
 19        pjs.pStop();
 20     }
      ...
 72  </script>

There is one important point to make. Consider the line

      var pjs = Processing.getInstanceById("KISA");

which appears in each function. This statement connects the Javascript to the Processing.js code. It uses the Processing.getInstanceById function using the identifier, "KISA", specified in the canvas tag. Of course any other identifier could be used.

After setting up the pjs variable, the corresponding p...() in the .pde file is called to carry out the desired operation.

One might ask, why doesn't on just include the var statement at the beginning of the script and avoid having to do it in every function. That would really simplify things but it doesn't work unfortunately if the .pde file has not been loaded before the statement is executed.

The jsChooseFile(i) is slightly different as it services clicks on all the file selection buttons. The argument i specifies the particular file clicked on.

 56     function jsChooseFile(i) {
 57        var pjs = Processing.getInstanceById("KISA");
 58        pjs.pChooseFile(i);
 59     }          

The final js... function, jsFileButtons(numButtons) is use to create the file buttons. It is called by the setup function in the .pde file. This was required because only the .pde file knows how many files have been specified. Later, setup uses a for loop to insert the file names into the .html file. First the code in the .html file.

 61     function jsFileButtons(numButtons) {   // called by Processing.js
 62         var i;
 63         var s = "";
 64         for (i = 0; i <  numButtons; i++) {
 65            var theId = "file" + i;
 66            s += '<input id="' + theId + '" type="radio" name="file" value="' + theId + '"'
 67                 + ' onclick="jsChooseFile(' + i + ')"><span id = "' + theId + 'Name">??</span><br>';
 68         }
 69         document.getElementById("putFileButtonsHere").innerHTML = s;
 70      }

When i = 0, the nasty looking lines 66 and 67 produce the following code:

    <input id="file0" type="radio" name="file0" value="file0" onclick="JsChooseFile(0)">
       <span id = "file0Name">??<span><br>

The function creates the specified number of buttons but still does not know the names of the files. Hence it just puts "??" where the filename is supposed to be.

In the setup in the .pde file, the following code first calls the function jsFileButtons, then replaces the "??" with the file names, and finally checks the currently selected file.

 50    jsFileButtons(numFiles);
 51    for (i = 0; i < numFiles; i++)
 52      document.getElementById('file'+ i + 'Name').innerHTML = files[i];
 53    document.getElementById('file'+ currentFile).checked = true;

Experimenting with the proposed HTML5 meter tag and form's range input

meters

The draft version of HTML5 includes a <meter> tag that would be great for displaying the volume and current time. It also includes a range input (a slider) for forms that would a convenient way to adjust the volume and current time. Unfortunately as of 1/30/13 only Chrome and Opera implement both completely. Safari 5.1 does a nice job with the range input but does not implement the meter graphically. Firefox 18 does the opposite. Internet Explorer 9 does not implement either graphically. If your browser doesn't implement these items you may see nothing or may see a text box with the value. The image shown in this sections shows the meters and range inputs as they appear in Chrome 23. Lets see how these items can be used. First the lines in the .html file:

101       Volume: 
102            <meter id="volumeMeter" min="0" max="1" value="1.00" ></meter>  
103            <span id="volumeText">100%</span>
104       <br>
105       Current Time: 
106            <meter id="currentTimeMeter" min="0" value="0" /></meter>
107            <span id="currentTimeText"> 0.0/1.0 sec</span>
108       <br>
109        Volume:
110            <input id="volumeRange" type="range" value = "100" min = "0" max = "100" 
111                 onchange="jsVolumeChange()">
112        <br>
113        Time:
114            <input id="timeRange" type="range" value = "0" min = "0" max = "100" 
115                 onchange="jsTimeChange()">  

Volume values are between 0 and 100 so the volume meter's and the range's min and max attributes are set accordingly. KISA uses the default value of 100 for the initial value of the volume. The minimum and initial value for the current time will be 0. The maximum value will be reset by the .pde file after it is known.

We will use the draw method in the .pde file to keep the meter and range graphics updated. In order to avoid having to call the getElementById every time the meter and range graphics are redrawn, some global variables are declared and then evaluated in the draw method.

 25  Element volumeMeter, volumeValue, currentTimeMeter, currentTimeText,
 26          volumeRange, timeRange;
...
 55    // get meter and input range elements
 56    volumeMeter = document.getElementById("volumeMeter");
 57    volumeValue = document.getElementById("volumeValue");
 58    currentTimeMeter = document.getElementById("currentTimeMeter");
 59    currentTimeText = document.getElementById("currentTimeText");
 60    volumeRange = document.getElementById("volumeRange");
 61    timeRange = document.getElementById("timeRange");

The draw method calls the setVolumeDisplay and setCurrentTimeValue methods which actually do the work. It didn't seem necessary to update the meter and range graphics 20 times a second so, in order to reduce overhead, they are called every 5 times the sketch is redrawn. setVolumeGraphics and setCurrentTimeGraphics methods send the appropriate values sent to the web document. The volume values were rounded to the nearest percent. The volume values were rounded to the nearest percent and the time values to the nearest 10th second. You might prefer to round the time values to the nearest second. The step is also set in case the audio file lasts less than 10 seconds. The following code includes a section from the draw method, and concludes with the two methods.

 82    if (frameCount % 5 == 0) {
 83      try {  // designed to solve a timing problem - drawn before setup finished
 84        setVolumeGraphics(audio.volume);
 85        setCurrentTimeGraphics(audio.currentTime, audio.duration);
 86      } catch (Exception e) {
 87      }
 88    }
...
185    // volumeMeter, volueValue, volumeRange are in .html file
186    try {
187      double v, v100;
188      v = round(100 * vol)/100;
189      v100 = 100 * v;
190      volumeMeter.value = vol;
191      volumeText.innerHTML = v100 + "%";
192      volumeRange.value = v100;
193    } catch (Exception e) {
194    }
195  }  // setVolumeGraphics
196   
197  void setCurrentTimeGraphics(double cur, double dur) {
198    // currentTimeMeter and timeRange are in .html file
199    double c, d;
200    try {
201      c = round(10 * cur)/10;
202      d = round(10 * dur)/10;
203      currentTimeMeter.value = c;
204      currentTimeMeter.max = d;
205      currentTimeText.innerHTML = c + "/" + d + " sec";
206      timeRange.value = c;
207      timeRange.max = d;
208      if (d < 10)
209        timeRange.step = .1;
210      else
211        timeRange.step = 1.
212    } catch (Exception e) {
213    }
214  }  // setCurrentTimeGraphics   
 

We need to explain the try/catch structures. In at least one browser (Chrome), if the code in the setCurrentTimeValue method is executed before the length of the file is known, then parameter dur is "NaN" and causes an error when the currentTimeMeter is set. The try/catch statements prevented this problem. In addition, temporarily adding printlns to print the Exception e values in the catch sections helped finding bugs during debugging.

The meter and range graphics have additional attributes that can be used. CSS can be used to improve the looks of these graphics but this was not done in keeping with "KIS".

Two alternatives


Alternative 1: Getting access to the Processing.js audio object

Alternative 2: Sophisticated onclick commands


We now look at two alternative coding styles that can reduce the amount of coding needed.

While Javascript code cannot look at variable Processing.js variable "audio" directly, there is a simple way to provide access indirectly by means of a "get" function. This method might particularly appealing to Javascript programmers. In the .pde file:

217  // An alternative:
218  // The following method allows Javascript access to the audio object
219  Audio getAudio() { return audio; }

Javascript can now use this method to access the audio object. For example, here is an alternate way to handle the "Play" button. The pPlay method in the .pde file is no longer needed. In the .html file:

121          <input type="button" value="Play" onclick="jsPlay1()">
122        <script type="text/javascript">
123  	    function jsPlay1() {
124              var pjs = Processing.getInstanceById("KISA");
125              pjs.getAudio().play();	  
126  	    }
127        </script>

The second method can be used to eliminate all the js...() Javascript functions at the cost of somewhat complicating the "onclick' code. It works by essentially compacting the js...() function code into the onclick attribute. For example, the "Pause" button could be handled as follows:

132           <input type="button" value="Pause" 
133                      onclick="Processing.getInstanceById('KISA').pPause()">  


< Previous (KISA 5)   Next (KISA 6) >

[+/-] The KISA5F.html file

  1  <!DOCTYPE html>  
  2  <html>
  3  <head>
  4    <title>KISA 5 Form (Keep It Simple Audio)</title>
  5    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  6    <script type="text/javascript" src="processing.min.js"></script>
  7    <script type="text/javascript">
  8      
  9     function jsPlay() {
 10        var pjs = Processing.getInstanceById("KISA");
 11        pjs.pPlay();	  
 12  	}
 13     function jsPause() {
 14        var pjs = Processing.getInstanceById("KISA");
 15        pjs.pPause();
 16     }
 17     function jsStop() {
 18        var pjs = Processing.getInstanceById("KISA");
 19        pjs.pStop();
 20     }
 21     function jsVolumeDown() {
 22        var pjs = Processing.getInstanceById("KISA");
 23        pjs.pVolumeDown();
 24     }
 25     function jsVolumeUp() {
 26        var pjs = Processing.getInstanceById("KISA");
 27        pjs.pVolumeUp();
 28     }
 29     function jsTimeDown() {
 30        var pjs = Processing.getInstanceById("KISA");
 31        pjs.pTimeDown();
 32     }
 33     function jsTimeUp() {
 34        var pjs = Processing.getInstanceById("KISA");
 35        pjs.pTimeUp();
 36     }
 37     function jsLoop() {
 38        var pjs = Processing.getInstanceById("KISA");
 39        pjs.pLoop();
 40     }
 41     function jsVolumeChange() {
 42        var x = document.getElementById("volumeRange").value;
 43        var pjs = Processing.getInstanceById("KISA");
 44        pjs.pVolumeChange(x);
 45     }
 46     function jsTimeChange() {
 47        var x = document.getElementById("timeRange").value;
 48        var pjs = Processing.getInstanceById("KISA");
 49        pjs.pTimeChange(x);
 50     }
 51  
 52     function jsAutoplay() {
 53        var pjs = Processing.getInstanceById("KISA");
 54        pjs.pAutoplay();
 55     }
 56     function jsChooseFile(i) {
 57        var pjs = Processing.getInstanceById("KISA");
 58        pjs.pChooseFile(i);
 59     }          
 60  
 61     function jsFileButtons(numButtons) {   // called by Processing.js
 62         var i;
 63         var s = "";
 64         for (i = 0; i <  numButtons; i++) {
 65            var theId = "file" + i;
 66            s += '<input id="' + theId + '" type="radio" name="file" value="' + theId + '"'
 67                 + ' onclick="jsChooseFile(' + i + ')"><span id = "' + theId + 'Name">??</span><br>';
 68         }
 69         document.getElementById("putFileButtonsHere").innerHTML = s;
 70      }
 71   
 72  </script>
 73  </head>
 74  <body>
 75    <h1>Keep It Simple Audio 5 Form</h1>
 76      <audio id="audioTag"  class="border" preload="auto" width="420">
 77            Your browser does not support HTML5 audio.
 78      </audio>
 79  
 80    <canvas id="KISA" data-processing-sources="KISA5F.pde">
 81      Your browser does not support HTML 5.  You may want to update your
 82      browser.
 83    </canvas>
 84  
 85    <form>
 86       <input type="button" value="Play" onclick="jsPlay()">
 87       <input type="button" value="Pause" onclick="jsPause()">
 88       <input type="button" value="Stop" onclick="jsStop()">
 89       <br>
 90       <input type="button" value="Volume down" onclick="jsVolumeDown()">
 91       <input type="button" value="Volume up" onclick="jsVolumeUp()">
 92       <br>
 93       <input type="button" value="Time down" onclick="jsTimeDown()">
 94       <input type="button" value="Time up" onclick="jsTimeUp()">
 95       <br>
 96       <input id="loopBox" type="checkbox" value="Loop" onclick="jsLoop()">Loop &nbsp;
 97       <input id="autoplayBox" type="checkbox" value="Autoplay" onclick="jsAutoplay()">Autoplay
 98       <br>
 99        <div id="putFileButtonsHere"><br>reserved for file buttons and names<br></div>
100       <br>
101       Volume: 
102            <meter id="volumeMeter" min="0" max="1" value="1.00" ></meter>  
103            <span id="volumeText">100%</span>
104       <br>
105       Current Time: 
106            <meter id="currentTimeMeter" min="0" value="0" /></meter>
107            <span id="currentTimeText"> 0.0/1.0 sec</span>
108       <br>
109        Volume:
110            <input id="volumeRange" type="range" value = "100" min = "0" max = "100" 
111                 onchange="jsVolumeChange()">
112        <br>
113        Time:
114            <input id="timeRange" type="range" value = "0" min = "0" max = "100" 
115                 onchange="jsTimeChange()">  
116        <br>
117       <!-- ******************************************************* -->
118        <div style = "background-color:  #FFEEEE; width: 500px">
119        <p> Alternative 1:  Getting access to the Processing.js audio object
120        </p>
121          <input type="button" value="Play" onclick="jsPlay1()">
122        <script type="text/javascript">
123  	    function jsPlay1() {
124              var pjs = Processing.getInstanceById("KISA");
125              pjs.getAudio().play();	  
126  	    }
127        </script>
128        </div>
129        <div style = "background-color:  #EEEEFF; width: 500px">
130         <p>Alternative 2:  Sophisticated onClick commands
131         </p>  
132           <input type="button" value="Pause" 
133                      onclick="Processing.getInstanceById('KISA').pPause()">  
134        </div>
135        <!-- ******************************************************* -->
136       
137    </form>
138  
139  </body>
140  </html>

[+/-] The KISA5F.pde file

  1  // KISA
  2  // Keep It Simple Audio version 5 Form
  3  // James Brink  brinkje@plu.edu
  4  // 3/15/12   4/12/12   1/29/13
  5  // The constants can be modified.
  6  int width = 500;  // Width of canvas
  7  int height = 165; // 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 = document.getElementById("audioTag");
 20  
 21  int time;
 22  String audioStatus;
 23  boolean repeatLoop = false;  // Should the player loop?
 24  
 25  Element volumeMeter, volumeValue, currentTimeMeter, currentTimeText,
 26          volumeRange, timeRange;
 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    audioStatus = "";
 46    setSource(files[currentFile]);  // pick the initial audio file
 47    // set HTML form items
 48    document.getElementById('loopBox').checked = audio.loop;
 49    document.getElementById('autoplayBox').checked = audio.autoplay;
 50    jsFileButtons(numFiles);
 51    for (i = 0; i < numFiles; i++)
 52      document.getElementById('file'+ i + 'Name').innerHTML = files[i];
 53    document.getElementById('file'+ currentFile).checked = true;
 54  
 55    // get meter and input range elements
 56    volumeMeter = document.getElementById("volumeMeter");
 57    volumeValue = document.getElementById("volumeValue");
 58    currentTimeMeter = document.getElementById("currentTimeMeter");
 59    currentTimeText = document.getElementById("currentTimeText");
 60    volumeRange = document.getElementById("volumeRange");
 61    timeRange = document.getElementById("timeRange");
 62  } // setup
 63  
 64  void draw() {
 65    // Draws the sketch on the canvas
 66    background(#FFFFAA);
 67    fill(#000000);
 68    if (audio == null) {
 69      text("Your browser does not handle the HTML 5 audio tag.  You ", 20, 30);
 70      text("may want to upgrade your browser to the current version.", 20, 60);
 71    return;
 72    }
 73    
 74    textAlign(CENTER);
 75    text("KISA 5F Using HTML Form Input", width/2, 30);
 76    text("Source file: " + audio.src, width/2, 60);
 77    text("Status: " + audioStatus, width/2, 80);
 78    text("Current time: " + round(audio.currentTime)
 79         + " sec.   Length: " + round(audio.duration)+ " sec.", width/2, 100);
 80    text("Volume (0 to 1): " + round(10 * audio.volume)/10.0, width/2, 120);
 81    text("Loop: " + repeatLoop + "   Autoplay: " + audio.autoplay, width/2, 145);
 82    if (frameCount % 5 == 0) {
 83      try {  // designed to solve a timing problem - drawn before setup finished
 84        setVolumeGraphics(audio.volume);
 85        setCurrentTimeGraphics(audio.currentTime, audio.duration);
 86      } catch (Exception e) {
 87      }
 88    }
 89  }  // draw
 90  
 91  void setSource(String url) {
 92    // Called to set the source file.  Do not include the file extension
 93    // in the url.  It will be added here.
 94    if (audio.canPlayType && audio.canPlayType("audio/ogg")) {
 95      audio.setAttribute("src", url + ".ogg");
 96    } else {
 97      audio.setAttribute("src", url + ".mp3");
 98    }
 99    audioStatus = "File selected";
100  } // setSource
101  
102  void stop() {
103    // Called stop playing the file by pausing it and setting the
104    // time back to 0;
105    audio.pause();
106    audio.currentTime = 0;
107    audioStatus = "Stopped";
108  }  // stop
109  
110  void loadStart() {
111    // LoadStart event processing
112    audioStatus = "Loading";
113  }  // loadStart
114  
115  void playing() {
116    // Playing event processing
117    audioStatus = "Playing";
118  }  // playing
119  
120  void pause() {
121    // Pause event processing.
122    // There is no stop event but a "stop" causes a pause.  This
123    // method checks to see if the current time = 0. If so it assumes
124    // stopped
125    if (audio.currentTime == 0)
126      audioStatus = "Stopped";
127    else
128      audioStatus = "Paused";
129  }  // pause
130  
131  void ended() {
132    int j;
133    // Ending event processing
134    if (repeatLoop) {
135      currentFile = (currentFile+1) % numFiles;
136      setSource(files[currentFile]);
137      document.getElementById('file'+currentFile).checked = true;
138      audio.play();
139    } else {
140      audioStatus = "Finished playing";
141    }
142  }  // ended
143  
144  void error() {
145    // error event processing
146    audioStatus = "Error";
147  }  // error
148  
149  void pPlay()  { audio.play(); }
150  void pPause() { audio.pause(); }
151  void pStop()  { stop(); }
152  
153  void pVolumeDown() {
154    double v = audio.volume - 0.1;
155    audio.volume = constrain(v, 0, 1);
156  }
157  void pVolumeUp() {
158    double v = audio.volume + 0.1;
159    audio.volume = constrain(v, 0, 1);
160  }
161  void pVolumeChange(double v) {
162    audio.volume = v/100;
163  }
164  void pTimeDown() {
165    double t = audio.currentTime - 0.1 * audio.duration;
166    audio.currentTime = constrain(t, 0, audio.duration);
167  }
168  void pTimeUp() {
169    double t = audio.currentTime + 0.1 * audio.duration;
170    audio.currentTime = constrain(t, 0, audio.duration);
171  }
172  
173  void pTimeChange(double t) {
174    audio.currentTime = t;
175  }
176  void pLoop() { repeatLoop = !repeatLoop; }
177  void pAutoplay() { audio.autoplay = !audio.autoplay; }
178  
179  void pChooseFile(int i) {
180    currentFile = i;
181    setSource(files[currentFile]);
182  }
183  
184  void setVolumeGraphics(double vol) {
185    // volumeMeter, volueValue, volumeRange are in .html file
186    try {
187      double v, v100;
188      v = round(100 * vol)/100;
189      v100 = 100 * v;
190      volumeMeter.value = vol;
191      volumeText.innerHTML = v100 + "%";
192      volumeRange.value = v100;
193    } catch (Exception e) {
194    }
195  }  // setVolumeGraphics
196   
197  void setCurrentTimeGraphics(double cur, double dur) {
198    // currentTimeMeter and timeRange are in .html file
199    double c, d;
200    try {
201      c = round(10 * cur)/10;
202      d = round(10 * dur)/10;
203      currentTimeMeter.value = c;
204      currentTimeMeter.max = d;
205      currentTimeText.innerHTML = c + "/" + d + " sec";
206      timeRange.value = c;
207      timeRange.max = d;
208      if (d < 10)
209        timeRange.step = .1;
210      else
211        timeRange.step = 1.
212    } catch (Exception e) {
213    }
214  }  // setCurrentTimeGraphics   
215  
216  
217  // An alternative:
218  // The following method allows Javascript access to the audio object
219  Audio getAudio() { return audio; }

Updated 11/6/14