npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ugfoundation/swaralipi-js

v0.2.1

Published

JSON spec, Unicode-Bengali viewer, and Tone.js player for Rabindrasangeet swaralipi (আকারমাত্রিক notation).

Downloads

1,168

Readme

swaralipi-js

@ugfoundation/swaralipi-js — a JSON spec, a Unicode-Bengali viewer, a Tone.js player, and an NLTR converter for Rabindrasangeet swaralipi (Tagore's আকারমাত্রিক / akarmatrik notation).

The spec is the contract: write (or generate) a swaralipi JSON document, then hand the same document to the viewer, the player, or the printer. Built for gitabitan.io, published standalone under GPL-2.0-only.

pnpm add @ugfoundation/swaralipi-js      # or npm / yarn

Quick start

The smallest useful program: validate a document and render it.

import { parseSwaralipi } from '@ugfoundation/swaralipi-js/spec';
import { renderToHtml, SWARALIPI_CSS } from '@ugfoundation/swaralipi-js/viewer';

const doc = parseSwaralipi({
  version: '0.1.0',
  meta: { title: 'অকারণে', taal: { id: 'dadra', numBeats: 6 }, tonic: 'C' },
  parts: [{ name: 'asthayi', rows: [{ notes: [
    { svara: ['madhyam', 'm'], lyric: 'অ' },
    { svara: ['madhyam', 'p'], lyric: 'কা' },
    { sustain: true, lyric: '৹' },
    { bar: 'single' },
  ] }] }],
});

document.head.insertAdjacentHTML('beforeend', `<style>${SWARALIPI_CSS}</style>`);
document.body.innerHTML = renderToHtml(doc, { lang: 'bn' });

Writing a swaralipi document

A document is a single JSON object: { version, meta, parts }. A part is a section (আস্থায়ী / অন্তরা …) holding rows (written lines); a row holds notes (the tokens below). Build it by hand, generate it from a tool, or produce it with the converter. Everything else in this package consumes this shape.

{
  "version": "0.1.0",
  "meta": { /* see below */ },
  "parts": [
    { "name": "asthayi", "rows": [ { "notes": [ /* tokens */ ] } ] }
  ]
}

meta

Only title is required. The rest is optional but drives playback and the header.

"meta": {
  "title": "অকারণে অকালে মোর পড়ল যখন ডাক",
  "taal": { "id": "dadra", "numBeats": 6, "bhaags": [3, 3], "sam": 0, "khaali": [3] },
  "taalName": "দাদরা",        // verbatim source name (use when `taal.id` is unknown)
  "tonic": "C",               // base pitch — "C", "C#"/"cs", "Db" all accepted
  "bpm": 144,                 // default tempo, matra/min (player only; viewer ignores it)
  "avartan": 2,               // আবর্তন count, when the source states it
  "notationBy": "…", "source": "…"   // provenance
}

svara — pitched notes

A pitch is a [octave, letter] tuple. Octaves: ati-mandra, mandra, madhyam, taar, ati-taar. Letters: s r g m p d n (d=ধৈবত/dha, n=নিষাদ/ni). Sharps/flats are flags, not letters:

{ "svara": ["madhyam", "g"] }                       // শুদ্ধ গ
{ "svara": ["madhyam", "g"], "komal": true }        // কোমল গ (flat)
{ "svara": ["madhyam", "m"], "tivra": true }        // কড়ি/তীব্র ম (sharp)
{ "svara": ["taar", "s"] }                          // taar সা (high)
{ "svara": ["mandra", "p"] }                        // mandra প (low)
{ "svara": ["madhyam", "r"], "microtone": "atikomal" }  // sub-komal: alpakomal | atikomal

duration — matra groups

Default duration is 1 মাত্রা. Use durationMatra for fractional or held notes; a run of fractions summing to a whole matra renders as one beamed group (nsfa → নর্সা):

{ "svara": ["madhyam", "n"], "durationMatra": 0.5 }   // half-matra
{ "svara": ["taar", "s"],    "durationMatra": 0.5 }   // …grouped with the one above
{ "svara": ["madhyam", "p"], "durationMatra": 2 }     // held two matras

sustain (আ-কার) and melisma

{ "sustain": true, "lyric": "৹" }                       // hold prev pitch+vowel (আ-কার)
{ "svara": ["taar", "s"], "sustain": true, "lyric": "৹" } // melisma: pitch moves, vowel continues

rest (বিরাম)

{ "rest": true, "durationMatra": 1 }                    // silence for one matra

bars — দাঁড়ি / দণ্ড

{ "bar": "dari" }     // ।   vibhag separator inside the cycle
{ "bar": "single" }   // I   end of one taal cycle (ফেরা)
{ "bar": "double" }   // II  section / return-to-asthayi boundary
{ "bar": "quad" }     // IIII end of the song

brackets — repeat / skip / alt

A bracket spans a range; the first note carries edge: 'open', the last edge: 'close'. repeat = {} গুন্ডবন্ধনী · skip = () omit on repeat · alt = [] changed-on-repeat.

{ "svara": ["madhyam", "s"], "brackets": [{ "kind": "repeat", "edge": "open" }] }
{ "svara": ["madhyam", "r"], "brackets": [{ "kind": "repeat", "edge": "close" }] }

kan (grace) and meend (glide)

{ "svara": ["madhyam", "n"], "kan": ["taar", "s"] }   // কণ grace before the note
{ "svara": ["madhyam", "p"], "meend": "start" }       // মীড় slur start …
{ "svara": ["madhyam", "g"], "meend": "end" }         // … and end

lyrics and annotations

lyric rides on the note (column-aligned, per-syllable). above / below attach recursive notes for alt-on-repeat passages, stop marks, or beat labels:

{ "svara": ["madhyam", "m"], "lyric": "অ",
  "above": [{ "svara": ["madhyam", "p"] }] }           // alt note printed above

raw — lossless passthrough

Anything the converter can't decode is preserved verbatim in raw and rendered dimmed — the spec is lossless by construction, nothing is silently dropped. You rarely write raw by hand.

{ "raw": "w", "below": [/* … */] }

Validate what you wrote

import { parseSwaralipi, safeParseSwaralipi } from '@ugfoundation/swaralipi-js/spec';

parseSwaralipi(json);            // returns typed Swaralipi, throws ZodError if invalid
safeParseSwaralipi(json);        // { success, data | error } — no throw

See fixtures/ for worked documents — octaves, microtones, matra-durations, repeat-brackets, and the hand-checked akarone anchor.


Deploying the document

View: <swaralipi-sheet>

Renders the document as Unicode Bengali + a CSS grid — no images, no embedded font, no framework. Arcs, kan, bars, beat labels and annotations all render.

<script type="module">
  import { defineSwaralipiSheet } from '@ugfoundation/swaralipi-js/viewer';
  defineSwaralipiSheet();
</script>

<swaralipi-sheet src="/notation/4270.json" lang="bn"></swaralipi-sheet>

Pass a URL via src or set the doc property directly. lang is bn | hi | en (Devanagari for hi, roman + octave dots for en); hide-header; highlight('part.row.note') for player sync. Styles are shadow-DOM-encapsulated, themeable via --sw-font, --sw-size, --sw-cell-gap, --sw-hl.

SSR / static sites (Astro etc.)renderToHtml is a pure function (string in, string out, no DOM); emit it alongside SWARALIPI_CSS. See Quick start.

Print / PDF

import { printSwaralipi, renderPrintDocument } from '@ugfoundation/swaralipi-js/viewer';

printSwaralipi(doc, { lang: 'bn', title: 'song-name' });   // hidden-iframe → print dialog
const page = renderPrintDocument(doc);                     // full HTML string (new tab / headless)

document.querySelector('swaralipi-sheet').print() is the element shortcut. PDF fidelity comes from the browser's own engine — the same Indic shaping (reph, conjuncts, matra) and CSS-drawn marks as the screen — so we deliberately avoid html2canvas/jsPDF, which can't shape Indic text.

Play: SwaralipiPlayer

A Tone.js player turns a document into audio — melody on santoor / harmonium / violin / flute / grand piano, a tabla loop (metronome for unrecorded taals), and a tanpura drone — and drives the viewer's highlight in sync. Tone is dynamically imported on first play(), so importing the module never touches AudioContext — safe in Node/SSR.

import { SwaralipiPlayer } from '@ugfoundation/swaralipi-js/player';

const player = new SwaralipiPlayer();        // zero-config: samples from the project CDN
player.load(doc);
player.bindSheet(document.querySelector('swaralipi-sheet'), { autoScroll: true });

playButton.onclick = () => player.play();    // must be a user gesture (AudioContext)

Controls: setTonic('C#'), setBpm(120), setInstrument(id), setTaal(id|null), setRhythm('tabla'|'metronome'|'none'), setTanpura(bool), pause() / stop() / dispose(). Each song loads only the samples covering its pitch range.

  • Self-host samples: override soundsBaseUrl (layout: santoor/<note>.mp3, tabla/<taal>/<taal><bpm>bpm.mp3, tanpura/<pc>.mp3).
  • Add instruments: customInstruments: { sitar: { label, urls, baseUrl } }.
  • Liveness: await player.checkInstruments(){ id: available }, to grey out picker options whose samples are down.

The pure, audio-free scheduler is exported too (buildSchedule, svaraToMidi, parseTonic, INSTRUMENTS, …) for a custom transport.

Export audio (WAV / MP3)

Render a document to a downloadable audio file entirely in the browser — an OfflineAudioContext bounces the same melody / tabla / tanpura arrangement faster than real time, so nothing hits a server (works on Chrome, Firefox, Safari incl. iOS, Edge, Android, PWAs).

import { exportAudio } from '@ugfoundation/swaralipi-js/player';

const { blob, filename } = await exportAudio(doc, {
  melody: true,        // the notation voice (default on)
  rhythm: 'tabla',     // 'tabla' | 'metronome' | 'none'  (default 'none')
  taal: 'auto',        // a taal id, or 'auto' from meta.taal
  tanpura: true,
  format: 'mp3',       // 'wav' (default) | 'mp3'
});
Object.assign(document.createElement('a'), { href: URL.createObjectURL(blob), download: filename }).click();

Also honours instrument, tonic, bpm, mp3Kbps (192), channels (mono by default — the content is monophonic), sampleRate, and soundsBaseUrl. WAV is 16-bit PCM and needs no dependency; MP3 needs the optional peer dependency @breezystack/lamejs — see License.

Convert: NLTR HTML → JSON

NLTR's Rabindra Rachanabali renders swaralipi as HTML grids in the ASCII-encoded Swarabitan webfont. The converter decodes that codec into spec JSON, losslessly. Across the full 1,738-song corpus: 100% of files convert, 99.95% of notation cells decode semantically, the rest preserved raw and reported.

swaralipi convert 4270.html                          # spec JSON to stdout
swaralipi convert nltr/html -o out/ --report out/_report.json
import { convertNltrHtml } from '@ugfoundation/swaralipi-js/convert';   // Node-only subpath
const { doc, report } = convertNltrHtml(html, { nltrId: 4270 });

Subpath exports

| Import | Environment | |--------|-------------| | @ugfoundation/swaralipi-js (root, re-exports /spec) | any | | @ugfoundation/swaralipi-js/spec | any | | @ugfoundation/swaralipi-js/viewer | browser / SSR | | @ugfoundation/swaralipi-js/player | browser (Node/SSR-safe import) | | @ugfoundation/swaralipi-js/convert | Node only | | swaralipi CLI | Node |


Develop

pnpm install
pnpm build   # tsup → dist/ (ESM + CJS + .d.ts)
pnpm test    # vitest
pnpm check   # tsc --noEmit

Node 20 LTS (.nvmrc), pnpm 9. Contributions are welcome under a CLA — see CONTRIBUTING.md.

Credits

  • NLTR (Society for Natural Language Technology Research) — the Rabindra Rachanabali digital edition and its Swarabitan-encoded swaralipi are the source corpus this package decodes. Tagore's notation itself is in the public domain.
  • sargam-spec by Studio Kalavati (EPL-2.0) — swaralipi-js's spec is an independent reimplementation that extends sargam-spec's format; we keep its [octave, letter] svara tuple and octave/letter conventions.
  • Audio samples, all CC-BY: the default Salamander Grand Piano by Alexander Holm; harmonium / violin / flute from tonejs-instruments by Nicholaus Brosowsky; santoor / tabla / tanpura / metronome redistributed on the project CDN.

License

GPL-2.0-only © Upal & Gopa Das Foundation. See LICENSE.

Optional MP3 export. exportAudio(…, { format: 'mp3' }) uses @breezystack/lamejs, which is LGPL-3.0 — a different licence from this package. It is declared an optional peer dependency, is loaded only via a dynamic import() when you actually request MP3, and is never bundled or redistributed by swaralipi-js, so it does not affect this package's GPL-2.0-only licensing. Install it yourself only if you want MP3 output; WAV export needs no extra dependency. If you ship an application bundle that includes lamejs, meeting its LGPL-3.0 terms (e.g. allowing the library to be replaced) is your responsibility.