Page Example

Piano Sample Overview

The Piano sample application demonstrates how you can create a piano application using the W3C Audio API.

The following figure illustrates the main screen of the Piano.

Figure: Piano screen

Piano screen

The application opens with the main screen, where you can see the piano keyboard. Click the keys to hear the sound.

Source Files

You can create and view the sample application project including the source files in the IDE.

File name Description
config.xml This file contains the application information for the platform to install and launch the application, including the view mode and the icon to be used in the device menu.
index.html This is a starting file from which the application starts loading. It contains the layout of the application screens.
css/style.css This file contains the CSS styling for the application UI.
js/main.js This file contains the code for handling the playing functionality of the application.
images/ This directory contains the images used to create the user interface.
sounds/ This directory contains sounds used by the application.

Implementation

The Piano sample application defines the Piano class that is used after the application starts.

/* main.js */
var piano;

function Piano() 
{
   'use strict';

   return;
}

/*  Definition of the Piano class */
(function strict() 
{
   'use strict';
}());

piano = new Piano();

When the DOMContentLoaded event occurs, the init() method of the Piano object is called. It is responsible for application initialization and calls three other methods of Piano object: cacheImages(), bindEvents(), and audioInit().

/* main.js */
/* Definition of the Piano class */
(function strict() 
{
   'use strict';

   Piano.prototype.init = function init() 
   {
      this.cacheImages();
      this.bindEvents();
      this.audioInit();
   };
}());

document.addEventListener('DOMContentLoaded', function onDocumentReady() 
{
   'use strict';
   piano.init();
});

The cacheImages() method loads all of the images that are used in the application to the browser memory. It prevents flickering during the first display of each image.

/* main.js */
/* Definition of the Piano class */
(function strict() 
{
   'use strict';

   Piano.prototype = 
   {
      /**
      * Cache for pressed white piano key image.
      * @type HTMLImageElement
      */
      whiteCache: null,
      /**
      * Cache for pressed black piano key image.
      * @type HTMLImageElement
      */
      blackCache: null
   };

   Piano.prototype.cacheImages = function cacheImages() 
   {
      this.whiteCache = new Image();
      this.whiteCache.src = 'images/white_pressed.png';
      this.blackCache = new Image();
      this.blackCache.src = 'images/black_pressed.png';
   };
}());

The audioInit() function initializes array of HTML audio elements, and defines its names and audio source files. Each HTML audio element corresponds to a specified piano keyboard key and is assigned to its own audio source file. The audio files are located in sounds folder of the application project.

/* main.js */
/* Definition of the Piano class */
(function strict() 
{
   'use strict';

   Piano.prototype = 
   {
      /**
      * Audio tags for keys.
      * @type Array
      */
      audio: [],
   };

   Piano.prototype.audioInit = function audioInit() 
   {
      var i;
      for (i = 0; i <= 13; i += 1) 
      {
         this.audio[i] = document.createElement('audio');
         this.audio[i].name = i;
         this.audio[i].src = 'sounds/' + this.audio[i].name + '.wav';
      }
   };
}());

The bindEvents() method binds all UI events used in application to its handler functions. In order to play sounds, the application uses handlers, which correspond to touch events such as touchstart, touchmove, and touchend.

/* main.js */
/* Definition of the Piano class */
(function strict() 
{
   'use strict';

   Piano.prototype.bindEvents = function bindEvents() 
   {
      var keys = document.getElementsByTagName('body')[0]; /* Keys container */

      keys.addEventListener('touchstart', this.onPianoKeyTouchDown.bind(this));

      keys.addEventListener('touchmove', this.onPianoKeyTouchDown.bind(this));

      keys.addEventListener('touchend', this.onPianoKeyTouchUp.bind(this));

      window.addEventListener('tizenhwkey', function onTizenHwKey(e) 
      {
         if (e.keyName === 'back') 
         {
            try 
            {
               tizen.application.getCurrentApplication().exit();
            } 
            catch (err) 
            {
               console.log('Error: ', err);
            }
         }
      });
   };
}());

The onPianoKeyTouchDown() method handles the touchstart and touchmove events on keys of the piano keyboard. It iterates through the changedTouches array supplied with the event object. For each touch element it checks whether the selected UI element is already touched. For this purpose it uses the global touchPianoKey array that stores information about touched key and corresponded touch identifier. As a result, the sound is played at first touch on the selected key of the piano keyboard. In such case the touchPianoKey array is updated and the selected keyboard key changes appearance to pressed (pressed CSS class added to the UI element). There is also the releaseKey() method that is called with some timeout. This method changes the appearance of the keyboard key to released, if there is no information about this key in the touchPianoKey array. In this case, the releaseKey() method is useful when the touchmove event occurs and it is necessary to change the appearance of the previously touched key while touch moving through the keyboard keys.

/* main.js */
/* Definition of the Piano class */
(function strict() 
{
   'use strict';

   Piano.prototype = 
   {
      /**
      * Maps multitouch fingers to piano keys.
      * @type Array
      */
      touchPianoKey: [],
   }; 

   Piano.prototype.onPianoKeyTouchDown = function onPianoKeyTouchDown(eventData) 
   {
      eventData.preventDefault();
      var touch,
          element,
          len = eventData.changedTouches.length,
          i,
          lastKeyId;

      for (i = 0; i < len; i += 1) 
      {
         /* Find multitouch finger touch event */
         touch = eventData.changedTouches[i];
         /* Find piano key under this finger */
         element = document.elementFromPoint(touch.clientX, touch.clientY);

         if (element && this.touchPianoKey[touch.identifier] !== element.id) 
         {
            /* Add/modify mapping for multitouch finger identifier to the piano key */
            lastKeyId = this.touchPianoKey[touch.identifier];
            this.touchPianoKey[touch.identifier] = element.id;
            /* Release the last key under this finger */
            setTimeout(this.releaseKey.bind(this, lastKeyId), 10);
            /* Play audio file for this piano key */
            this.playAudio(element.id.substr(3));
            /* Change key's state to pressed */
            element.classList.add('pressed');
         }
      }
   };
}());

The playAudio() method plays sound for given audio ID. For this purpose it uses the previously initialized array of audio tags.

/* main.js */
/* Definition of the Piano class */
(function strict() 
{
   'use strict';

   Piano.prototype.playAudio = function playAudio(audioId) 
   {
      var audio = this.audio[audioId];
      if (audio) 
      {
         if (audio.currentTime !== 0) 
         {
            audio.currentTime = 0;
         }
         if (audio.paused) 
         {
            audio.play();
         }
      }
   };
}());

The releaseKey() method checks whether the information about keyboard key with given ID is not inside the touchPianoKey array. In such case, the appearance of the key is changed to released.

/* main.js */
/* Definition of the Piano class */
(function strict() 
{
   'use strict';

   Piano.prototype.releaseKey = function releaseKey(keyId) 
   {
      /* Change key's state to normal */
      var stillPressed = false,
          len = this.touchPianoKey.length,
          i;

      if (keyId === undefined) 
      {
         return;
      }

      /* Check for every multitouch event fingers */
      for (i = 0; i < len; i += 1) 
      {
         if (this.touchPianoKey[i] === keyId) 
         {
            /* This piano key is still pressed with another finger */
            stillPressed = true;
            break;
         }
      }
      /* If key is no longer pressed, change its state to normal */
      if (!stillPressed) 
      {
         document.getElementById(keyId).classList.remove('pressed');
      }
   };
}());

The onPianoKeyTouchUp() method handles the touchend event on the keys of the piano keyboard. It iterates through the changedTouches array supplied with the event object. For each touch element it removes the information about the key and corresponded touch from the touchPianoKey array. At the end the releaseKey() method is called to change the appearance of the released key.

/* main.js */
/* Definition of the Piano class */
(function strict() 
{
   'use strict';

   Piano.prototype.onPianoKeyTouchUp = function onPianoKeyTouchUp(eventData) 
   {
      var touchId,
          keyId,
          len = eventData.changedTouches.length,
          i;
      for (i = 0; i < len; i += 1) 
      {
         /* Find multitouch finger touch event identifier */
         touchId = eventData.changedTouches[i].identifier;
         /* Find piano key that was pressed */
         keyId = this.touchPianoKey[touchId];
         /* Remove mapping for multitouch finger identifier to the piano key */
         this.touchPianoKey[touchId] = undefined;
         /* Release the piano key */
         setTimeout(this.releaseKey.bind(this, keyId), 10);
      }
   };
}());