Saturday, 27 November 2021

 

How to create a simple UI library using decorators

Preface

Lately I've been looking for a light-weight library that helps to develop UI components using native browser technologies like Web Components, Shadow DOM etc. I see most of them surviving out there like Angular, React are not completely based on native technologies and not only that they have quite some learning curve. Also, there is always this fear that, am I doing this in the right way?. I found libraries like LitElement compelling but finally I ended up creating something simple, light-weight that focuses only on solving the component building business and nothing else.

I created a small library called tiny that utilizes decorators and a simple base class to create components using native technologies. I'm pleased by the outcome. Though it doesn't support fancy data bindings and other cool features (for now) it turned out to be good!. In this article, I would like to share some of the learnings I got in the process of building that library and hopefully someone can use them to build their own library in future and why not please share your thoughts and ideas to transform the tiny into something bigger.

Concept

The below code reveals the concept I had in my mind. It represents a simple web component that displays different emojis in different sizes based on inputs.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@element('my-smiley, `<span></span>`)
class SmileyElement extends BaseElement {
 
    @input()   
    type: string = 'happy';
 
    @input()   
    size = 'small' | 'medium' | 'large' = 'medium';
 
    @query('span')
    spanEl;
 
    onChanges(changes) {
        // TODO: perform DOM operations
    }
}

Listing 1. Concept

First of all, I love decorators! I hope you do too. I want to leverage them as much as possible to do the reusable business. You can see in the above snippet that we've applied a few decorators like elementinput and query.

The element decorator transforms the class into a web component. The input decorator as the name is used to mark the properties as inputs. The query decorator is used to automatically query and return the child element on accessing the applied property. We can have more fun with decorators, how about one to auto bind events to a function? Yes, we can! Let's keep things simple for now. Please check out the tiny github repo to refer to more decorators.

The other important thing to notice is, the SmileyElement extends from a base class BaseElement. To make the decorators work we got to do some plumbing and not only that we have other works too.. like rendering the passed template, helper methods to work with DOM etc. Most importantly, to register a class as a web component it should extend from HTMLElement or one of the built-in elements and the BaseElement extends from HTMLElement.

The base class also provides some life-cycle hooks for the component to intercept and act. As you see there is an onChanges method that'll be invoked every time there is a change in inputs and it is the place you need to perform the DOM operations. Since we don't have those cool data bindings we need to do the DOM updates manually. Don't worry, the base class provides a bunch of helper methods to make that process easier, effective and with absolute control.

Alright, let's set up the project and see how we can build those decorators first and then the base class.

To read more please visit http://prideparrot.com/blog/archive/2021/11/how_to_create_simple_ui_library

Saturday, 16 May 2020

musquito v3 — sounds made easy!

Introduction

musquito is an audio engine created using Web Audio API for HTML5 games and interactive websites. It provides a simple abstraction to create and play sounds easier.
Below are some of the core features supported by the library.
  • Built on the powerful Web Audio API
  • Simple API to create and play sounds
  • Supports sound groups
  • Supports variety of codecs
  • Supports audio sprites
  • Supports streaming using HTML5 audio nodes
  • Fading
  • Caching
  • Auto Garbage Management

Browser Support

  • Google Chrome
  • Firefox
  • Safari
  • Opera
  • Microsoft Edge

Installation

At this time musquito is available only in npm and you can install it using the below command.
npm install musquito --save
Listing 1. Installing musquito using npm
You can also directly reference files available from the distribution folder.
<script src="musquito/dist/musquito.min.js"></script>
Listing 2. Referencing musquito file using script tag

"Hello World" Example

A simple example of how to create and play a gun fire sound.
import $buzz from 'musquito';

$buzz.play('gun.mp3');
Listing 3. Playing a gun fire sound using ES6 syntax

Passing Additional Parameters

The below example shows how you can pass additional parameters like volume, rate and callback.
$buzz.play({
  src: ['greenade.mp3', 'greenade.wav'],
  volume: 0.5,
  rate: 2,
  playEndCallback: () => alert('Playback started')
});
Listing 4. Passing additional parameters

Using Sprites

Audio Sprites are like image sprites concatenates multiple small sounds in a single file. You can create audio sprite using this tool.
Below is an example of how to use sprites.
$buzz.play({
  src: 'sprite.mp3',
  sprite: {
    "beep": [
      0,
      0.48108843537414964
    ],
    "button": [
      2,
      2.4290249433106577
    ],
    "click": [
      4,
      4.672018140589569
    ]
  },
  sound: 'beep'
});
Listing 6. Playing sounds using sprites

Pausing and Stopping Sounds

Calling the play method returns the sound id and you can use it to call other methods like pause, stop, change the volume and more properties of the sound.
  
const soundid = $buzz.play('bg.mp3');

// Pausing sound
$buzz.pause(soundid);

// Stopping sound
$buzz.stop(soundid);
Listing 7. Pausing and stopping sounds

Fading Sounds

You can fade the volume of a playing sound linearly or exponentially as shown below.
const soundid = $buzz.play('bg.mp3');

setTimeout(() => {
  $buzz.fade(soundid, 0, 3);
}, 2000);
Listing 8. Fading sounds

Streaming

Long audio files can be played using HTML5 audio nodes by passing stream option as true.
 
$buzz.play({
  src: 'bg.mp3',
  stream: true
});
Listing 9. Streaming

Advanced Example

The below example shows how we can setup audio engine by passing audio resources with shorthand identifiers initially before playing sounds. The setup method also takes lot of other arguments to configure the engine, please refer the API docs.
$buzz.setup({
  src: {
    bg: 'bg.mp3',
    sprite: {
      url: 'sprite.mp3',
      sprite: {
        "beep": [
          0,
          0.48108843537414964
        ],
        "button": [
          2,
          2.4290249433106577
        ],
        "click": [
          4,
          4.672018140589569
        ]
      }
    }
  },
  oninit: () => {
    // Playing sounds with identifiers
    $buzz.play('#bg');
    $buzz.play('#sprite.button');
  }
});
Listing 10. Advanced Example

Creating Audio Groups

Sometimes it's convenient to create a sound group which is called as "Buzz" that helps to create and manage multiple sounds for a single audio resource. Buzzes also supports events. The below example shows how we can create a sound group for a sprite and play multiple sounds easier.
  
const buzz = $buzz({
  src: 'sprite.mp3',
  sprite:{
    "beep": [
      0,
      0.48108843537414964
    ],
    "button": [
      2,
      2.4290249433106577
    ],
    "click": [
      4,
      4.672018140589569
    ]
  }
});

buzz.play('beep');
buzz.play('button');
buzz.play('click');
Listing 11. Creating audio groups

For complete documentation please check the website.
PLEASE GIVE A STAR TO THE REPO.

Monday, 25 June 2018

Introduction

musquito is an audio engine created using Web Audio API for HTML5 games and interactive websites. It provides a simple abstraction to create and play sounds easier.
Below are some of the core features supported by the library.
  • Simple API to create and play sounds
  • Supports variety of codecs
  • Supports audio sprites
  • Fading
  • Caching

Browser Support

  • Google Chrome
  • Firefox
  • Safari
  • Opera
  • Microsoft Edge

Installation

At this time musquito is available only in npm and you can install it using the below command.
 BASHCopynpm install musquito --save
                
You can also directly reference files available from the distribution folder.
 HTMLCopy<script src="musquito/dist/musquito-1.0.0.min.js"></script>
                

"Hello World" example

A simple example of how to create and play a gun fire sound.

ES6 and above

 JAVASCRIPTCopyimport $buzz from 'musquito';

const buzz = $buzz('gunfire.mp3');

buzz.play();
                

Classic JavaScript

 JAVASCRIPTCopyvar buzz = $buzz('gunfire.mp3');

buzz.play();
                

Advanced example

The below example shows how you can pass additional parameters like volume, rate and event handlers.
 JAVASCRIPTCopyconst buzz = $buzz({
  src: ['gunfire.mp3', 'gunfire.wav'],
  volume: 0.5,
  rate: 2,
  onplaystart: () => console.log('Playback started'),
  onplayend: () => console.log('Playback ended')
});

buzz.play();
                

Using Sprites

Audio Sprites are like image sprites concatenates multiple small sounds in a single file. You can create audio sprite using this tool.
Below is an example of how to use sprites.
 JAVASCRIPTCopyconst buzz = $buzz({
  src: 'gamesprite.mp3',
  sprite:{
    'gun': [0, 0.48],
    'bomb': [2, 2.42],
    'greenade': [4, 4.67]
  }
});

buzz.play('gun');
buzz.play('bomb');
                

Fading Sounds

You can fade the volume of a playing sound linearly or exponentially as shown below.
 JAVASCRIPTCopyconst buzz = $buzz({
  src: 'bg.mp3'
});

buzz.play();
...

buzz.fade(0, 3);
                

API

$buzz function

$buzz(args: string|Array<string>|object)

$buzz is the single API that helps you to create and manage sounds. It’s a function that returns a Buzz object. The Buzz object helps to control group of sounds created for a single audio source.
You can pass a single audio source, array of audio sources or an object. If an array of audio sources is passed then the first compatible one is picked for playing. If you need to pass additional information like initial volume, playback speed then you need to pass an object.

The different options you can pass in arguments object for the $buzz function.

NameTypeRequiredDefaultDescription
srcstring, Array<string>yesSingle or array of audio sources. If an array of audio sources is passed then the first compatible one is picked for playing.
idnumbernoAuto-generatedThe unique identifier for the Buzz object.
volumenumberno1.0The initial volume of the sound. The value should be from 0.0 to 1.0.
ratenumberno1.0The initial playback speed. The value should be from 0.5 to 5.0.
loopbooleannofalsePass true to play the sound repeatedly.
mutedbooleannofalsePass true to keep the sound muted initially.
preloadbooleannofalsePass true to pre-load the sound.
autoplaybooleannofalsePass true to play the sound at-once created.
formatstring, Array<string>nofalseSingle or array of audio formats for the passed audio sources.
spriteobjectnoThe sprite definition object that contains the starting and ending positions of each sound embedded in the sprite.
onloadfunctionnoThe event handler for “load” event.
onunloadfunctionnoThe event handler for “unload” event.
onplaystartfunctionnoThe event handler for “playstart” event.
onplayendfunctionnoThe event handler for “playend” event.
onstopfunctionnoThe event handler for “stop” event.
onpausefunctionnoThe event handler for “pause” event.
onmutefunctionnoThe event handler for “mute” event.
onvolumefunctionnoThe event handler for “volume” event.
onratefunctionnoThe event handler for “rate” event.
onseekfunctionnoThe event handler for “seek” event.
onerrorfunctionnoThe event handler for “error” event.
ondestroyfunctionnoThe event handler for “destroy” event.

Buzz object methods

MethodReturnsDescription
load()BuzzLoads the audio buffer.
play(soundOrId?: string, number)BuzzPlays a new sound or the passed sound defined in the sprite or the sound that belongs to the passed id.
pause(id?: number)BuzzPauses the sound belongs to the passed id or all the sounds belongs to this group.
stop(id?: number)BuzzStops the sound belongs to the passed id or all the sounds belongs to this group.
mute(id?: number)BuzzMutes the sound belongs to the passed id or all the sounds belongs to this group.
unmute(id?: number)BuzzUn-mutes the sound belongs to the passed id or all the sounds belongs to this group.
volume(volume?: number, id?: number)Buzz, numberGets/sets the volume of the passed sound or the group. The passed value should be from 0.0 to 1.0.
fade(to: number, duration: number, type = 'linear', id?: number)BuzzFades the volume of a playing sound or all sounds belongs to the group.
fadeStop(id?: number)BuzzStops the current running fade of the passed sound or all sounds belongs to the group.
rate(rate?: number, id?: number)Buzz, numberGets/sets the rate of the passed sound or the group. The passed value should be from 0.5 to 5.0.
seek(id: number, seek?: number)Buzz, numberGets/sets the current playback position of the sound.
loop(loop?: boolean, id?: number)Buzz, booleanGets/sets the looping behavior of a sound or the group.
playing(id: number)booleanReturns true if the passed sound is playing.
muted(id?: number)booleanReturns true if the passed sound is muted or the group is muted.
state(id?: number)BuzzState, SoundStateReturns the state of the passed sound or the group.
duration(id?: number)numberReturns the duration of the passed sound or the total duration of the sound.
unload()BuzzUnloads the loaded audio buffer.
destroy()BuzzStops and destroys all the sounds belong to this group and release other dependencies.
on(eventName: string, handler: function, once = false, id?: number)BuzzSubscribes to an event for the sound or the group.
off(eventName: string, handler: function, id?: number)BuzzUn-subscribes from an event for the sound or the group.
id()numberReturns the unique id of the sound.
loadState()LoadStateReturns the audio resource loading status.
isLoaded()booleanReturns true if the audio source is loaded.
sound(id: number)SoundReturns the sound for the passed id.
alive(id: number)booleanReturns true if the passed sound exists.

$buzz static / global methods

These are wrapper methods of engine that helps to control the audio globally. You can invoke this method by $buzz.[methodname](args).
MethodReturnsDescription
setup(args?: object)$buzzSets-up the audio engine.
load(urls: string, Array<string>)PromiseLoads single or multiple audio resources into audio buffers and returns them.
unload(urls: string, Array<string>)$buzzUnloads single or multiple loaded audio buffers from cache.
mute()$buzzMutes the engine.
unmute()$buzzUn-mutes the engine.
volume(vol?: number)$buzz, numberGets/sets the volume for the audio engine that controls global volume for all sounds.
stop()$buzzStops all the currently playing sounds.
suspend()$buzzStops all the playing sounds and suspends the engine immediately.
resume()$buzzResumes the engine from the suspended mode.
terminate()$buzzShuts down the engine.
muted()booleanReturns whether the engine is currently muted or not.
state()EngineStateReturns the state of the engine.
context()AudioContextReturns the created audio context.
isAudioAvailable()booleanReturns true if Web Audio API is available.
on(eventName: string, handler: function, once = false)$buzzSubscribes to an event.
off(eventName: string, handler: function)$buzzUn-subscribes from an event.

License

MIT