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.
| Option | Default | Means |
|---|---|---|
context | required | an AudioContext |
destination | context.destination | where the sound goes |
bank | BANK | the folder of bol samples |
bols | data/bols.js | bol → strokes |
bpm | 120 | matras 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. |
cents | 0 | fine tuning of both drums |
sam | null | 'bell', 'manjeera' or 'stick' rings on every sam. Change it with await setSam(name). |
samVolume | 1 | the sam cue's own gain |
filler | null | 'light', 'busy' or 'auto': light, and busy every 4th filler |
fillerEvery | 4 | every nth cycle is a filler |
vibrate | false | a short buzz on every beat. Android only. |
pulse | 30 | ms per buzz. Raise it if a phone's motor misses beats. |
onBeat | null | (beat, time, section), see below |
A variation with one kind of filler plays that kind.
Methods
| Method | Does |
|---|---|
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.
beatcounts the section's cells. A tihai can span two cycles.timeis the audio time the matra sounds.sectionis'theka','fillerLight','fillerBusy'or'tihai'. It is'end'when a tihai lands.
Other exports
| Export | Is |
|---|---|
compile(taal, variation?, section?, bols?) | a section's timed strokes { at, dur, stroke }, in matras. No audio. |
TONICS | the 12 scales, G … F# |
STROKES | the bank's 37 sample names |
BANK | the 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
| Field | Means |
|---|---|
id | the file name, e.g. kehrwa |
en, bn, hi | its name in English, Bengali and Hindi |
matra | matras per cycle |
bhaags | matras per vibhag, e.g. [4, 4] |
sam, taali, khali | matras, counted from 1 |
bols | optional. Bol → strokes, over data/bols.js. |
variations | a list. The first is the default. |
Cells
One cell per matra. Bols in a cell share the matra equally.
| Write | Means |
|---|---|
dha ge | two bols, half a matra each |
dha*3 ge | dha takes three shares, ge one |
na+ke | two strokes at once |
- or — | a rest |
LDNA | a 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
| Field | Means |
|---|---|
name | <taal> 1, 2, … unless the style has its own name |
theka | the cycle that loops. Required. |
fillerLight, fillerBusy | a cycle that replaces the theka now and then |
tihai | the closing tihai: one or two cycles |
tihaiArrival | strokes on the sam after the tihai |
thekaBol … tihaiArrivalBol | labels to show instead of the cells |
tempo | a starting BPM |
aliases | swaralipi taal ids that pick this variation, e.g. surphank |
Add a taal
- Copy a file in
data/taals/. Name it after the new id. - Edit it. Write one cell per matra.
- 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).
| Option | Default | Means |
|---|---|---|
tonic + octave | 'C', 3 | Sa, 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. |
second | null | an optional fifth string, e.g. 'ni' |
style | 5 | beats per round: 5 north, 6 south |
bpm | 105 | plucks per minute, 40 to 180 |
levels | [1, 1, 1, 1, 1] | gain of each string: first, second, Sa, Sa, kharaj |
alt | false | a sharper jawari for the C# set |
cents | 0 | fine tuning |
onPluck | null | (string, time), up to 0.1 s before each pluck |
- Bare
STRINGSnames lie between kharaj and Sa.-lowand-highsit an octave off. await tune({ tonic, octave, voice, alt })moves Sa once its strings are fetched.- The C#3 sample set covers Sa below F#3, the G#3 set the rest.
NOTES: C … B.TANPURA_BANK: the default sample folder.
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)
- It keeps the tabla's time, beat by beat. The pattern restarts on every sam.
manjeeraFor(matra)lists the patterns that fit a taal. There are 32.- A cell holds
o(open),x(closed) or-(rest). Its strokes share the matra. beat(beat, time, seconds)plays one matra.stop(when?)lets it ring out.
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()
| Option | Default | Means |
|---|---|---|
raag | RAAGS[0] | { name, aroha, avaroha }, from RAAGS or your own |
direction | 'aroha' | or 'avaroha' |
every | 5 | seconds between strums. 0 strums once. |
bpm | 530 | strings per minute |
tonic | 'C' | Sa, as for the tabla |
cents | 0 | fine tuning |
onStrum | null | (time), up to 0.1 s before each strum |
RAAGSholds 117 raags. A line readsS r R g G M M# P d D n N;'is an octave up.swars(line): each swar's semitones above Sa.strum(time)strums once, from audio timetime.
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:
- the taal's own
theka(and fillers, tihai,bols), orvariations: a custom taal - the catalogue taal with that id or alias
- that taal repeated to fill the cycle (kawali: 2 × qawwali)
- any taal with the same vibhags
- 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.
| Export | Folder |
|---|---|
BANK | https://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
- Start audio from a click or tap. Browsers block it before that.
- iPhone on silent: set
navigator.audioSession.type = 'playback'first, as music apps do. - Safari before 17.2 (iOS 16) can't import JSON. Fetch the taal there.
- Every instrument keeps time from a tiny Web Worker, so it plays on with the screen locked.
Under a Content Security Policy, allow
worker-src blob:. Without it, a plain timer runs instead. vibrateworks on Android only. No iOS version hasnavigator.vibrate. Chrome skips it while the phone is on silent.
Taals
Tap a name to play it in the app.
| Taal | বাংলা | हिन्दी | id | Matras | Vibhags | Variations |
|---|
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.