tabla-js

Docs

Plain ES modules on Web Audio. No dependencies, no build step.

Install · Tabla · Taal JSON · Tanpura · Manjeera · Swarmandal · Swaralipi · Samples · Browsers · Taals

Install

npm i @ugfoundation/tabla-js

No bundler? Import it from jsDelivr:

import { Tabla } from 'https://cdn.jsdelivr.net/npm/@ugfoundation/tabla-js@0/src/index.js'

Types ship in src/index.d.ts.

Tabla

import { Tabla } from '@ugfoundation/tabla-js'
import kehrwa from '@ugfoundation/tabla-js/data/taals/kehrwa.json' with { type: 'json' }

const tabla = new Tabla({ context: new AudioContext(), tonic: 'D', sam: 'bell', filler: 'light', vibrate: true })
await tabla.load(kehrwa, 'Kaharwa 2') // no name = the first variation
tabla.bpm = 97 // matras per minute
tabla.start()
tabla.tihai() // after this cycle: tihai, land on sam, stop

Options

Options are also properties. bpm, tonic, filler and cents can change while it plays.

OptionDefaultMeans
contextrequiredan AudioContext
destinationcontext.destinationwhere the sound goes
bankBANKthe folder of bol samples
bolsdata/bols.jsbol → strokes
bpm120matras per minute
tonic'C'Sa of the dayan, 'G' up to 'F#' (TONICS). Pitching also stretches each stroke's decay a little.
bayan'C#'the bayan's own scale. 'C#' is as recorded.
cents0fine tuning of both drums
samnull'bell', 'manjeera' or 'stick' rings on every sam. Change it with await setSam(name).
samVolume1the sam cue's own gain
fillernull'light', 'busy' or 'auto': light, and busy every 4th filler
fillerEvery4every nth cycle is a filler
vibratefalsea short buzz on every beat. Android only.
pulse30ms per buzz. Raise it if a phone's motor misses beats.
onBeatnull(beat, time, section), see below

A variation with one kind of filler plays that kind.

Methods

MethodDoes
await load(taal, variation?)compiles a variation and fetches its samples. No name: the first variation.
start(when?, from?)plays from audio time when, from matras after sam. For a pickup or a seek.
tihai()after this cycle: the tihai. It lands on sam, rings out and stops.
stop(when?)stops at audio time when. Strokes due before it still play.
await setSam(name)the sam cue; null turns it off

onBeat

onBeat(beat, time, section) fires up to 0.1 s before each matra sounds.

Other exports

ExportIs
compile(taal, variation?, section?, bols?)a section's timed strokes { at, dur, stroke }, in matras. No audio.
TONICSthe 12 scales, G … F#
STROKESthe bank's 37 sample names
BANKthe default sample folder

Taal JSON

A taal is one file in data/taals/. Its name is the taal id.

{
  "id": "kehrwa", "en": "Kaharwa", "bn": "কাহারবা", "hi": "कहरवा",
  "matra": 8, "bhaags": [4, 4], "sam": 1, "taali": [1], "khali": [5],
  "variations": [
    {
      "name": "Kaharwa 1",
      "theka": ["dha", "dhi", "na", "ti", "na", "dhi", "dha", "dhi"],
      "tihai": ["dhi na", "tete kata", "dha", "dhi na", "tete kata", "dha", "dhi na", "tete kata"],
      "tihaiArrival": "dha"
    }
  ]
}

Taal fields

FieldMeans
idthe file name, e.g. kehrwa
en, bn, hiits name in English, Bengali and Hindi
matramatras per cycle
bhaagsmatras per vibhag, e.g. [4, 4]
sam, taali, khalimatras, counted from 1
bolsoptional. Bol → strokes, over data/bols.js.
variationsa list. The first is the default.

Cells

One cell per matra. Bols in a cell share the matra equally.

WriteMeans
dha getwo bols, half a matra each
dha*3 gedha takes three shares, ge one
na+ketwo strokes at once
- or —a rest
LDNAa sample by name, not a bol

A stroke rings until its drum plays again. Some cells are strokes, not bols: one bol sounds many ways.

Variation fields

FieldMeans
name<taal> 1, 2, … unless the style has its own name
thekathe cycle that loops. Required.
fillerLight, fillerBusya cycle that replaces the theka now and then
tihaithe closing tihai: one or two cycles
tihaiArrivalstrokes on the sam after the tihai
thekaBol … tihaiArrivalBollabels to show instead of the cells
tempoa starting BPM
aliasesswaralipi taal ids that pick this variation, e.g. surphank

Add a taal

  1. Copy a file in data/taals/. Name it after the new id.
  2. Edit it. Write one cell per matra.
  3. Run ./test-local.sh. It checks every variation. The taal shows up on the test page and in the app.

No code? The app's composer builds a taal and exports this JSON.

Tanpura

import { Tanpura } from '@ugfoundation/tabla-js'

const tanpura = new Tanpura({ context, tonic: 'C#', octave: 3, first: 'ma' })
await tanpura.load()
tanpura.start()
await tanpura.tune({ tonic: 'D' }) // fetches a new sample set if needed

Five strings: first, second, Sa, Sa, kharaj. Each pluck rings out. Tuned in just intonation (Pa 3/2, Ma 4/3).

OptionDefaultMeans
tonic + octave'C', 3Sa, A2 to E4
voice—'low' (A2–G#3) or 'high' (an octave up, to E4), instead of octave
first'pa'a key of STRINGS: 'ma', 'ni', 'komal-ni', … null is silent.
secondnullan optional fifth string, e.g. 'ni'
style5beats per round: 5 north, 6 south
bpm105plucks per minute, 40 to 180
levels[1, 1, 1, 1, 1]gain of each string: first, second, Sa, Sa, kharaj
altfalsea sharper jawari for the C# set
cents0fine tuning
onPlucknull(string, time), up to 0.1 s before each pluck

Manjeera

import { Manjeera, manjeeraFor } from '@ugfoundation/tabla-js'

const manjeera = new Manjeera({ context, pattern: manjeeraFor(8)[0].pattern })
await manjeera.load()
tabla.onBeat = (beat, time, section) => section !== 'end' && manjeera.beat(beat, time, 60 / tabla.bpm)

Swarmandal

import { Swarmandal, RAAGS } from '@ugfoundation/tabla-js'

const swarmandal = new Swarmandal({ context, raag: RAAGS.find((r) => r.name === 'Yaman'), tonic: 'C', every: 10 })
await swarmandal.load() // again after changing raag, direction or tonic
swarmandal.start()
OptionDefaultMeans
raagRAAGS[0]{ name, aroha, avaroha }, from RAAGS or your own
direction'aroha'or 'avaroha'
every5seconds between strums. 0 strums once.
bpm530strings per minute
tonic'C'Sa, as for the tabla
cents0fine tuning
onStrumnull(time), up to 0.1 s before each strum

Swaralipi

import { fromSwaralipi, Tabla } from '@ugfoundation/tabla-js'

const { taal, variation } = await fromSwaralipi(doc.meta.taal)
await tabla.load(taal, variation)

It turns a swaralipi-js taal ({ id, numBeats, bhaags, sam, khaali, variation }) into a tabla-js taal. The cycle is always numBeats long. It picks, in order:

  1. the taal's own theka (and fillers, tihai, bols), or variations: a custom taal
  2. the catalogue taal with that id or alias
  3. that taal repeated to fill the cycle (kawali: 2 × qawwali)
  4. any taal with the same vibhags
  5. a plain theka on its vibhags, with generated: true

Every taal in gitabitan.io's swaralipi JSON plays a whole cycle. It fetches taals from CATALOGUE: this release on jsDelivr. Pass a second argument to host them yourself.

Samples

Every sound loads from bitoron, the project's asset CDN. Pass bank to host them yourself.

ExportFolder
BANKhttps://bitoron.pages.dev/audio/instruments/tabla/bols/, plus the sam sounds
TANPURA_BANK…/instruments/tanpura/strings/
MANJEERA_BANK…/instruments/manjeera/
SWARMANDAL_BANK…/instruments/swarmandal/

Browsers

Taals

Tap a name to play it in the app.

Taalবাংলাहिन्दीidMatrasVibhagsVariations

App

The app is a small PWA on the library: the practice player and a taal composer. No build step. It runs offline once installed, and plays on with the screen off.

Contributing

Contributions are welcome under a CLA. See CONTRIBUTING.md. GPL-2.0-only © Upal & Gopa Das Foundation.