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

@real-music-packages/web-core

v0.17.0

Published

Shared music-theory + audio primitives for the music-suite web apps

Readme

@real-music-packages/web-core

Shared, framework-agnostic music-theory primitives for the music-suite web apps (stave-web-sightread / RealSightReader and realeartrainer-web). Pure TypeScript — no DOM, Tone.js, Svelte, or OSMD. Published publicly on npmjs.com.

Modules

Core (default import /)

  • notesNOTE_NAMES, NOTE_NAMES_FLAT, noteNameToIndex(name), pitchClass(midi), midiToNoteName(midi, useFlats?)
  • frequencymidiToFrequency(midi) (equal temperament, A4=440)
  • enharmonicKEYS_PREFER_FLATS, useFlatsForKeyName(key), useFlatsForKeyFifths(fifths) (bridges RET's key-name model and Stave's keyFifths model)
  • scalesMAJOR_SCALE_INTERVALS, ALL_KEYS, getMidiNote(degree, key, octave?), getScaleDegree(midi, key), isInScale(midi, key), getStability(degree)
  • intervalsINTERVALS, intervalBySemitones(n)
  • chordsCHORD_TEMPLATES (quality → interval set, richer qualities first)

./streak

localStorage-backed practice streak tracker — SSR-safe (silently no-ops when localStorage is absent). createStreak(key, storage?) returns a StreakStore with get(), record(today?), and live(today?). Injected fake storage makes unit tests straightforward. todayKey() returns today as YYYY-MM-DD in local time. Storage shape: {current, longest, last} — identical to RSR's existing key so migration is free.

./server

Cloudflare D1 signup handler shared by RSR and RET. handleSignup(request, db, appTag) handles POST {email, source?} → inserts into the shared signups D1 table with source = ${appTag}:${source}. Returns 405/400/500/200 with cache-control: no-store. The D1Like interface lets you pass any compatible D1 binding or a test fake.

./promo

Browser-only promo utilities (require Tone.js + opensheetmusicdisplay as optional peers; OSMD is only loaded via a dynamic import). Pure helpers (parseMidi, midiDurationMs) work in Node and are covered by unit tests.

  • parseMidi(buf) — minimal SMF parser: extracts note events with sustain-pedal extension and total duration.
  • renderNotation(xml, opts?) — renders a MusicXML string via OSMD to a detached canvas; returns per-staff measure boxes (RSR geometry), per-measure column union boxes (RMT geometry), system rows, and content bounds. Parameterisable via RenderNotationOpts (paper, inkSumThreshold, hostWidth, bars).
  • createPromoSampler(opts?) — creates a Tone.js Salamander sampler wired to a MediaStreamDestination. keepAlive option feeds a silent ConstantSource so the recorder never drops silent intro scenes (default false; RMT passes true).

./scene

Render-components: the Score model (scoreFromMusicXML), the Layer contract + SceneSpec runner, and the built-in layers (notation, scroll-cursor, keyboard, falling-notes, promo cards, spectrum, branding, and the S5 extended catalog).

This barrel is browser-safe — it pulls in no Node-only dependencies, so Vite/rolldown consumers need no aliases. scoreFromMusicXML runs unchanged in the browser (native canvas handles OSMD's lyric layout).

./scene/headless (Node-only)

setupHeadlessDom() — installs jsdom globals + a fake 2D canvas context so OSMD can load() a score outside a browser (CI, batch, audio-only paths). Import it only in Node, and call it once before scoreFromMusicXML (or pass opts.osmdFactory):

import { setupHeadlessDom } from '@real-music-packages/web-core/scene/headless'; // Node only
import { scoreFromMusicXML } from '@real-music-packages/web-core/scene';
await setupHeadlessDom();
const score = scoreFromMusicXML(xml);

This subpath references jsdom and must never be imported from browser code. It lives here (not in ./scene) precisely so the ./scene barrel stays bundler-safe.

⚠️ Octave-base gotcha (scales.getMidiNote / getScaleDegree)

These are ported verbatim from RealEarTrainer and use RET's non-standard octave base: getMidiNote(1, 'C', 4) === 48, i.e. one octave below the General-MIDI convention (where C4 = 60). They are internally consistent and RET-only. The general-purpose helpers (midiToNoteName, midiToFrequency, pitchClass) use the standard GM convention (C4 = 60). Don't mix getMidiNote's output with the standard helpers without accounting for the one-octave offset.

Install (consumers)

Published publicly on npmjs.com — no auth/token needed anywhere:

npm install @real-music-packages/web-core

Dev

npm install
npm test          # vitest
npm run build     # tsup → dist/ (ESM + .d.ts)

Publishing — auto-publishes from GitHub (no local npm publish)

To release: bump version in package.json and push to main. That's it.

The .github/workflows/publish.yml Action triggers on push to main (when src/**, package.json, or tsup.config.ts change), on v* tags, or manual dispatch. It is version-gated: it compares package.json version to what's on npm and only runs npm publish --access public when the local version is new (otherwise it no-ops). Auth uses the NPM_TOKEN repo secret — no local npm login/token is needed anywhere, and you do NOT run npm publish by hand.

Consumers (Stave, RET-web, Whozart, RMT) pin caret ranges like ^0.8.0, so after publishing a new minor you bump each consumer's pin + npm install to pick it up.

Practical note: pushing this repo's main IS the publish action. Don't push a version bump to main until the change is ready to go live to every consumer.