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

jpfun

v0.6.0

Published

A functional numbered musical notation typesetting engine

Readme

jpFun

A TypeScript engine that compiles a compact numbered-notation DSL into SVG or Canvas, with layout-independent playback.

Documentation

Features

  • Extensible music DSL with exact rational timing
  • Automatic spacing and pagination, shared by SVG and Canvas
  • Structured diagnostics and source-aware editing
  • Zero runtime dependencies; works in Node.js and modern browsers

Installation

npm install jpfun

jpFun is ESM-only, supports Node.js 18+ and modern browsers, and has no compiler DOM dependency.

Browser via CDN

Use the bundled jpfun global without a build step:

<script src="https://unpkg.com/jpfun"></script>
<script>
  const { compileScore, renderLayoutPagesToSvg } = jpfun;
  document.body.innerHTML = renderLayoutPagesToSvg(compileScore("1 2 3 | 4 -").layout)[0];
</script>

Or import an ES module:

<script type="module">
  import { compileScore } from "https://cdn.jsdelivr.net/npm/jpfun/+esm";
</script>

Pin [email protected] in production; these URLs otherwise use the latest release.

Optional bundles under https://unpkg.com/jpfun/dist/ share the same global:

| Bundle | Capabilities | | --- | --- | | jpfun.core.min.js | Compilation, rendering, playback, and source editing | | jpfun.from-musicxml.min.js | Standalone MusicXML import | | jpfun.from-midi.min.js | MIDI JSON import; automatic line breaking also requires the core bundle |

For standalone MIDI import, set a positive barsPerLine.

Quick Start

import { compileScore, renderLayoutPagesToSvg } from "jpfun";

const source = `
  @1(C4)
  @tempo(96)

  1/ 2/ 3 4 | 5 -
`;

const result = compileScore(source);
const pages = renderLayoutPagesToSvg(result.layout);

renderLayoutPagesToSvg returns one SVG string per page, ready to save or insert into a web page.

Note Source Transformations

transformNotes(source, lowering, operation, ranges?) returns sorted { span, text } edits for to-absolute, to-relative, semitone-up, or semitone-down. Use lowering from the same source and apply edits in one editor transaction or right to left.

Only notes fully covered by ranges are changed; omit it for the whole source, or pass [] for no changes. Conversion preserves pitch; semitone shifts preserve each note's representation. Key declarations, rests, spacers, percussion, and surrounding source are retained. Invalid note input throws a diagnostic.

Browser Rendering

The default text measurer estimates glyph widths. For accurate browser layout, pass CanvasTextMeasurer, including when rendering SVG:

import { compileScore, CanvasTextMeasurer, renderLayoutPagesToCanvas } from "jpfun";

const context = document.createElement("canvas").getContext("2d")!;
const result = compileScore(source, { textMeasurer: new CanvasTextMeasurer(context) });
renderLayoutPagesToCanvas(result.layout, [context]);

Canvas rendering takes one context per page. Measurement can use a separate canvas.

Compilation Pipeline

Source
  -> Parser     -> AST
  -> Lowering   -> timed events and relations
      |-> Playback -> NoteOn / NoteOff / Tempo events
       `-> Layout   -> document geometry
            -> Renderer -> SVG or Canvas

compileScore exposes parser, ast, lowering, and layout. Compile playback separately:

import { compilePlayback } from "jpfun";

const playback = compilePlayback(result.lowering);

The plan contains device-independent events, scoreMap, tracks, and durationSeconds, with exact Fraction timestamps. Repeat expansion exceeding 65,536 visited columns throws; raise the limit with { maxFlowSteps: 200_000 } when needed.

MIDI JSON Conversion

midiJsonToJpFun accepts the JSON returned by midi.js's JSON() method. Parse MIDI bytes in your application.

import { midiJsonToJpFun } from "jpfun";

const source = midiJsonToJpFun(parsedMidiJson, {
  title: "My Score",
  alignRate: 4,
  barsPerLine: 0,
});

pitchMode defaults to "absolute"; "relative" uses C-based numbered notation. alignRate controls quantization. barsPerLine defaults to 0 for automatic breaks; positive values fix the measures per system.

Preserves notes, tracks, tempo, time signatures, programs, and standard 3:2 quarter-, eighth-, and sixteenth-note triplets. Other tuplets are unsupported. Percussion channel 10, velocity, controllers, pitch bends, and lyrics are omitted; time-signature changes snap to measure boundaries.

MusicXML Conversion

musicXmlToJpFun accepts a parsed MusicXML root element. Use DOMParser in browsers or a compatible DOM implementation in Node.js:

import { musicXmlToJpFun } from "jpfun";

const xml = await file.text();
const document = new DOMParser().parseFromString(xml, "application/xml");
if (document.querySelector("parsererror")) throw new SyntaxError("Invalid MusicXML");

const { script, diagnostics } = musicXmlToJpFun(document.documentElement, {
  barsPerLine: 4,
});

The result contains the generated script and a MusicXmlDiagnostic[]. Callers that previously used the return value as a string must now read .script. Each diagnostic is a plain object with code, severity: "warning", message, and location; no diagnostics means an empty array. Conversion failures still throw instead of returning partial output.

| Code | Reported loss | | --- | --- | | W_MUSICXML_PERCUSSION_SKIPPED | Channel 10 notes omitted | | W_MUSICXML_APPROXIMATED | Display pitches used for unpitched notes, after-grace fallback, or wedges mapped to note onsets with a fixed velocity change | | W_MUSICXML_UNSUPPORTED_ELEMENT | Harmony and discarded children of supported notation or direction containers, including pedal, slur, unsupported articulations, ornaments, and dynamics | | W_MUSICXML_UNATTACHED_CONTENT | Grace notes, text, or dynamics with no target | | W_MUSICXML_UNRESOLVED_RELATION | Unmatched, replaced, unclosed, or unrenderable ties, wedges, and endings |

location.element is the XML tag name without its namespace prefix. Available context includes partId, measureIndex (1-based within the part), measureNumber (the original string), staff, and voice. These refer to the imported score, not offsets in script; XML line/column information is not available through the minimal DOM interface. Reports follow deterministic processing order, not necessarily XML document order.

Diagnostics cover the cases above, not the entire MusicXML specification. Empty diagnostics do not guarantee lossless conversion. Ordinary defaults and detailed engraving coordinates do not produce warnings. Diagnostic messages describe the loss; use code for programmatic classification.

Text, dynamics, and wedges target the matching part and staff. With no voice specified, dynamics and wedges apply to matching voices on that staff, while text is attached only once. Missing targets produce diagnostics instead of redirecting the marking to another staff or voice. Tuplet markers are confirmed only when the resulting event rhythm retains their boundaries, including equivalent declarations on chord members.

All notations containers on a note are inspected. Tempo and dynamic marks that never reach the generated script, including terminal marks, produce unattached-content warnings. Arpeggios that cannot be emitted after chord merging and voice splitting, such as cross-staff groups reduced to single-note events, produce unsupported-element warnings.

Supports partwise/timewise scores, multiple voices, rhythm, lyrics, dynamics, repeats, and basic layout metadata. pitchMode defaults to "absolute"; "relative" uses the active MusicXML key.

Decompress .mxl archives before parsing. Percussion channel 10, pedal, cross-staff arpeggio grouping, and detailed engraving coordinates are omitted.

Syntax Example

@page(width=860px, left=38px, right=38px)
@1(C4)
@tempo(96)

N(Melody): 1/ 1/ 5/ 5/ 6/ 6/ 5 -
N: @div(C4 C4) G4/G4/ A4/ A/ G
L(Lyrics): do do so so la la so

See the language reference for the complete syntax.

Development

jpFun is maintained in a pnpm workspace. From the repository root:

pnpm install
pnpm run build:core
pnpm test
pnpm run dev

The development server opens the Playground at http://127.0.0.1:4173.

Documentation

License

Apache License 2.0