litePlay.js

A Start-Up Tutorial

litePlay.js is a JavaScript (JS) module for music lite coding. It can be used in any web applications, but typically we anticipate it to be helpful for interactive music programming. This may be oriented to performance or composition (or both). This tutorial is focused on such use cases.

JS Coding Environments

There are various online coding environments available for JS development with different characteristics and support for various kinds of uses. For the present tutorial, we are looking at the following requirements:

Not all environments fulfill these. The following three do:

The code in this tutorial can be run in any of the three platforms. Whenever the REPL is mentioned, if not present (e.g. in Playcode), the code can be evaluated directly from the script in the live mode.

Note that litePlay.js does not have any means of sound recording itself, it is designed strictly as a realtime, interactive, music coding environment. To record its output some means of browser audio capture needs to be used. At the time of writing the Google Chrome extension Audio Capture has been tested and can be used for this. Other means may also available depending on the operating system (e.g. BlackHole on MacOS).

Loading the LitePlay.js module

The simplest way to load the litePlay.js module is to add the following script tag to the main HTML page header (generally called index.html).

<script  src="https://vlazzarini.github.io/litePlay.js/litePlay.constants.js"></script>

When the page is loaded, you will have access to various system constants, as well as the following function:

lpRun();

which loads the module, starts the audio engine and tells the user that the system is ready to use when a message is printed to the JS console. Alternatively, a function f() can be used as the starting point of the litePlay.js application. Or it can simply contain all the code that is to be executed when the script is run,

function f() {
 // we start executing from here ...
}
lpRun(f);

Once the litePlay.js engine is running, we can use its functionality anywhere by prefixing its functions etc with lp..

An example startup JS script for the online P5.js editor is available at this URL.

The play() action

At the core of litePlay.js we have play(). This can be simply run as

lp.play();

to play a sound, and lp.stop() to stop it. These code lines can be typed directly on an interactive REPL console or added to the script.

We can make changes to this sound by setting the default instrument playing it,

lp.instrument(lp.organ);

the default was set to piano at the start, but we now changed it to organ. An introduction to instruments is given at the end of the tutorial.

We can also tell what to play. If the sound is from a pitched instrument, we can ask it to play a given pitch,

lp.play(E4);

The pitch symbolic names range from Cm1 ($=C_{-1}$) to G8 ($=G_{8}$), in such a way that the middle C of a piano keyboard is set to C4. The sound is played immediately and lasts indefinitely (although some may decay in intensity over time).

Accidentals are represented by lowercase s for sharp and b for flat notes:

lp.play(Eb4);
lp.play(Fs4);

Events

In litePlay.js we can think of the resulting action of play() in this form is a musical event. We can define it with five attributes:

Event attributes (or parameters) are optional, as we have seen. If we do pass them, defaults are used. It is possible to pass only a few parameters, for example just what; what and howLoud; what, howLoud, and when; as well as what, howLoud,when, and howLong.

Events are passed using a JS list (or array) with attributes in the order listed earlier

[what,  howLoud, when, howLong, onSomething]

For example,

lp.play([C4, 0.5, 0, 2, lp.violin])

The top-levelplay() action can take several events as arguments, such as

lp.play([C4, 0.1, 0, 3],[E4, 0.2, 0.5, 0.5], [G4, 0.4, 2, 0.1])

Event Lists

The last example of the previous section demonstrated that we can work with lists of events, instead of only sending individual events to play(). There is a particular aspect of this that we should note, the when attributes of each event will be interpreted in a certain way.

lp.play(C3, C4, C5);

In this case, the events will be separated by the default howLong for the onSomething being played (set to 1 sec). So we hear the sounds in sequence.

lp.play([C3], [C4], [C5]);

In this case, the default for when is 0, immediately, for all events in the list. We hear the sounds starting at the same time, mixed up.

lp.play([C3, 0.1, 0], [C4, 0.2, 1], [C5, 0.4, 2]);

In this case, the timing of events is relative to the time we asked for the event list to be played, with perhaps a very short delay. All events are precisely timed in relation to that. We can decide when these should come in with an exact time.

This brings us the concept of an eventList, which is expressed in litePlay.js by JS object that can be manipulated. For example, we can create it

evts = lp.eventList.create([C3, 0.1, 0], [C4, 0.2, 1], [C5, 0.4, 2]);

then we can play it

evts.play();

repeatedly (by calling play() on it). The play() can take two optional parameters,

eventList.play(when, events);

the first one is a when for the complete list, taken from the time of the action, so we can place the performance in the future. The second is a list of events, basically a JS list of lists, such as

[ [C3, 0.1, 0, 1], [C4, 0.2, 1, 1], [C5, 0.4, 2, 1]]

as can be seen, there is an outer lists which holds two events, which are themselves lists (of attributes). One fundamental aspect is that the event list passed to play() replaces the existing events (if any have been added) in the object. Beyond that, we may, amongst other things,

-eventList.add(event, ...) events to the end of the list. This can take any number of events (as create() did).

evts.add(event, [C4,0.9,3]);    
evts.remove(0);    
evts.insert(1, [D4, 0.8, 0.5]);

We can also repeat an eventList any number of times, when seconds later from the action,

evts.repeat(times, when);

Both eventList.play() and eventList.repeat() return the end time of the playback. So we can use that information to schedule other events. For example, this code plays an event list, adds two events to it and then schedule to repeat that longer sequence for three times after the end of the first play() action,

  let evt = lp.eventList.create([C4, 0.1, 0, 1],
                                [E4, 0.2, 1, 1], 
                                [G4, 0.4, 2, 1]);
  end = evt.play();
  evt.add([C1, 0.1, 3, 3], [C1, 0.1, 0, 4]);
  evt.repeat(3, end);

One consequence of all these ideas is that we have introduced the idea that a play() action may have different forms, depending on which context it is being invoked, which at the moment can be

With events and eventLists we can construct compositions and performances with precise relative timing between events, which is something desirable in musical activities.

Instruments

In litePlay.js, sound generation is managed by instrument objects. In general, we can access these by using one of the names defined in the JS module, like lp.piano, lp.organ, lp.synth, etc. These are constants provided by litePlay.js, allowing an easy selection of sound generators (to fulfill the onSomething attribute of an event).

This is the complete set of JS constant declarations for instruments. Note that each one of them has the form const instrName = new Instrument(number). There are 128 different instruments plus six special drum kits that can be accessed via their individual numbers.

const grandPiano = new Instrument(0);
const  piano = grandPiano;
const  brightPiano = new Instrument(1);
const  electricGrand = new Instrument(2);
const  honkyPiano = new Instrument(3);
const  electricPiano = new Instrument(4);
const  electricPiano2 = new Instrument(5);
const  harpsichord = new Instrument(6);
const  clavinet = new Instrument(7);
const  celesta = new Instrument(8);
const  glockenspiel = new Instrument(9);
const musicBox = new Instrument(10);
const vibraphone = new Instrument(11);
const marimba = new Instrument(12);
const xylophone = new Instrument(13);
const tubularBells = new Instrument(14);
const dulcimer = new Instrument(15);
const tinkleBell = new Instrument(112);
const drawbarOrgan = new Instrument(16);
const percussiveOrgan = new Instrument(17);
const rockOrgan = new Instrument(18);
const organ = rockOrgan;
const churchOrgan = new Instrument(19);
const reedOrgan = new Instrument(20);
const accordion = new Instrument(21);
const harmonic = new Instrument(22);
const tangoAccordion = new Instrument(23);
const nylonAcousticGuitar = new Instrument(24);
const guitar = nylonAcousticGuitar;
const steelAcousticGuitar = new Instrument(25);
const jazzElectricGuitar = new Instrument(26);
const clearElectricGuitar = new Instrument(27);
const mutedElectricGuitar = new Instrument(28);
const overdrivenGuitar = new Instrument(29);
const  distortionGuitar = new Instrument(30);
const  guitarHarmonics = new Instrument(31);
const  sitar = new Instrument(105);
const  banjo = new Instrument(106);
const  shamisen = new Instrument(107);
const  koto = new Instrument(108);
const  kalimba = new Instrument(109);
const  pizzicatoStrings = new Instrument(45);
const  orchestralHarp = new Instrument(46);
const harp = orchestralHarp;
const  acousticBass = new Instrument(32);
const  fingerElectricBass = new Instrument(33);
const  pickElectricBass = new Instrument(34);
const  fretlessBass = new Instrument(35);
const  bass = fretlessBass;
const  slapBass1 = new Instrument(36);
const  slapBass2 = new Instrument(37);
const  synthBass1 = new Instrument(38);
const  synthBass2 = new Instrument(39);
const  violin = new Instrument(40);
const  viola = new Instrument(41);
const  cello = new Instrument(42);
const  contrabass = new Instrument(43);
const  tremoloStrings = new Instrument(44);
const  stringEnsemble1 = new Instrument(48);
const  strings = stringEnsemble1;
const  stringEnsemble2 = new Instrument(49);
const  synthStrings1 = new Instrument(50);
const  synthStrings2 = new Instrument(51);
const  choirAahs = new Instrument(52);
const  voiceOohs = new Instrument(53);
const  synthVoice = new Instrument(54);
const  trumpet = new Instrument(56);
const  trombone = new Instrument(57);
const  tuba = new Instrument(58);
const  mutedTrumpet = new Instrument(59);
const  frenchHorn = new Instrument(60);
const  horn = frenchHorn;
const  brassSection = new Instrument(61);
const  brass = brassSection;
const  synthBrass1 = new Instrument(62);
const  synthBrass2 = new Instrument(63);

-Wind: pitched sounds created by blowing on different types of reeds

const  sopranoSax = new Instrument(64);
const  altoSax = new Instrument(65);
const  tenorSax = new Instrument(66);
const  baritoneSax = new Instrument(67);
const  oboe = new Instrument(68);
const  englishHorn = new Instrument(69);
const  bassoon = new Instrument(70);
const  clarinet = new Instrument(71);
const  piccolo = new Instrument(72);
const  flute = new Instrument(73);
const  recorder = new Instrument(74);
const  panFlute = new Instrument(75);
const  blownBottle = new Instrument(76);
const  shakuhachi = new Instrument(77);
const  whistle = new Instrument(78);
const  ocarina = new Instrument(79);

-Lead: lead-type pitched synthesizer sounds

const  lead1 = new Instrument(80);
const  lead2 = new Instrument(81);
const  lead3 = new Instrument(82);
const  lead4 = new Instrument(83);
const  lead5 = new Instrument(84);
const  lead6 = new Instrument(85);
const  lead7 = new Instrument(86);
const  lead8 = new Instrument(87);

-Synth: generic pitched synthesizer sounds

const  pad1 = new Instrument(88);
const  pad2 = new Instrument(89);
const  pad3 = new Instrument(90);
const  pad4 = new Instrument(91);
const  pad5 = new Instrument(92);
const  synth = pad5;
const  pad6 = new Instrument(93);
const  pad7 = new Instrument(94);
const  pad8 = new Instrument(96);
const  fx1 = new Instrument(97);
const  fx2 = new Instrument(98);
const  fx3 = new Instrument(99);
const  fx4 = new Instrument(100);
const  fx5 = new Instrument(101);
const  fx6 = new Instrument(102);
const  fx7 = new Instrument(103);
const  fx8 = new Instrument(104);

-Perc: mostly unpitched percussion sounds.

const  agogo = new Instrument(113);
const  steelDrums = new Instrument(114);
const  woodblock = new Instrument(115);
const  taikoDrum = new Instrument(116);
const  melodicTom = new Instrument(117);
const  synthDrum = new Instrument(118);
const  reverseCymbal = new Instrument(119);
const  guitarFretNoise = new Instrument(120);
const  breathNoise = new Instrument(121);
const  seaShore = new Instrument(122);
const  birdTweet = new Instrument(123);
const  telephoneRing = new Instrument(124);
const  helicopter = new Instrument(125);
const  applause = new Instrument(126);
const  gunshot = new Instrument(127);
const acousticBassDrum = 35;
const kick = acousticBassDrum;
const bassDrum1 = 36;
const sideStick = 37;
const acousticSnare = 38;
const handClap = 39;
const electricSnare = 40;
const snare = electricSnare;
const lowFloorTom = 41;
const closedHiHat = 42;
const highFloorTom = 43;
const pedalHiHat = 44;
const lowTom = 45;
const tom = lowTom;
const openHiHat = 46;
const lowMidTom = 47;
const hiMidTom = 48;
const crashCymbal = 49;
const crash = crashCymbal;
const hiTom = 50;
const rideCymbal1 = 51;
const cymbal = rideCymbal1;
const chineseCymbal = 52;
const rideBell = 53;
const tambourine = 54;
const splashCymbal = 55;
const cowbell = 56;
const crashCymbal2 = 57;
const vibraslap = 58;
const rideCymbal2 = 59;
const hiBongo = 60;
const lowBongo = 61;
const muteHiConga = 62;
const openHiConga = 63;
const lowConga = 64;
const hiTimbale= 65;
const lowTimbale = 66;
const hiAgogo = 67;
const lowAgogo = 68;
const cabasa = 69;
const maracas = 70;
const shortWhistle = 71;
const longWhistle = 72;
const shortGuiro = 73;
const longGuiro = 74;
const claves = 75;
const hiWoodBlock= 76;
const lowWoodBlock = 77;
const muteCuica = 78;
const openCuica = 79;
const muteTriangle = 80;
const openTriangle = 81;