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

@bassmoses/maestro-js

v0.2.2

Published

Human-friendly music library — write, render, and play music with simple syntax

Readme

Maestro.js

Write music. See music. Hear music.

A human-friendly JavaScript library that turns simple text notation into rendered sheet music and audio playback.

npm version license node

import { Song } from '@bassmoses/maestro-js'

const song = new Song()
song.add('C4 D4 E4 F4 G4')
song.render('#sheet')
song.play()

Three lines after import — renders a staff and plays the notes.


Install

npm install @bassmoses/maestro-js

No peer dependencies to configure. VexFlow and Tone.js are bundled internally.

Optional — only needed for exportPNG():

npm install sharp

Quick Start

Simple Melody

import { Song } from '@bassmoses/maestro-js'

const song = new Song({ tempo: 100, key: 'C' })
song.add('C4:q E4:q G4:q C5:q | G4:h E4:h | C4:w')
song.render('#sheet')
song.play()

Dynamics, Chords & Lyrics

import { Song } from '@bassmoses/maestro-js'

const song = new Song({
  tempo: 112,
  timeSignature: '4/4',
  key: 'D',
  instrument: 'piano',
})

// Melody with dynamics
song.add('D4:q(mp) F#4:q A4:h | A4:q(mf) G4:q F#4:h | E4:q(mp) F#4:q G4:q F#4:q | D4:w(p)')

// Chords
song.add('[D4 F#4 A4]:h(f) [A4 C#5 E5]:h | [G4 B4 D5]:h(ff) [A4 C#5 E5]:h(mf)')

// Lyrics attached to notes
song.add('C4:q"Hel" D4:q"lo" E4:h"world"')

song.render('#sheet-container', { width: 800, showDynamics: true })
song.play()

Full SATB Choir with Part Names

import { Song } from '@bassmoses/maestro-js'

const chorale = new Song({
  tempo: 76,
  timeSignature: '4/4',
  key: 'Bb',
  title: 'Sanctus',
  composer: 'Anonymous',
})

chorale
  .voice('soprano', { clef: 'treble' })
  .add('Bb4:h(mp) C5:q D5:q | Eb5:h D5:h | C5:q(mf) Bb4:q C5:h | Bb4:w(p)')

chorale
  .voice('alto', { clef: 'treble' })
  .add('F4:h G4:q F4:q | G4:h F4:h | Eb4:q(mf) F4:q Eb4:h | D4:w(p)')

chorale
  .voice('tenor', { clef: 'treble-8' })
  .add('D4:h Eb4:q F4:q | Bb4:h F4:h | G4:q(mf) F4:q G4:h | F4:w(p)')

chorale
  .voice('bass', { clef: 'bass' })
  .add('Bb2:h Eb3:q F3:q | Eb3:h Bb3:h | F3:q(mf) F2:q C3:h | Bb2:w(p)')

chorale.render('#score', {
  grandStaff: true,
  showBarNumbers: true,
  showPartNames: true, // labels each stave (default: true)
  partNameStyle: 'full', // 'full' or 'abbreviated' (default)
})
chorale.play()

Looping a Section

song.loop(2, 4) // repeat measures 2–4 during playback
song.play()
song.clearLoop() // remove the loop

Note Syntax at a Glance

| Feature | Syntax | Example | | :------------- | :---------------------------- | :------------------ | | Pitch + Octave | AG + 08 | C4, F#5, Bb3 | | Duration | :w :h :q :e :s :t | C4:q (quarter) | | Dotted | . after duration | G4:h. | | Rest | R | R:q | | Chord | [...] | [C4 E4 G4]:h | | Triplet | {...} | {C4 D4 E4}:q | | Dynamic | (pp) to (fff) | C4:q(mf) | | Tie | ~ | C4:h~C4:h | | Slur | (...) | (E4:q F4:q G4:h) | | Fermata | (fermata) | C4:w(fermata) | | Lyric | "text" after note | C4:q"hello" | | Barline | \| | C4:q D4:q \| E4:h | | Repeat | \|: ... :\| | \|: C4:q D4:q :\| | | Da Capo | D.C. | C4:w \| D.C. |

Full syntax reference: docs/syntax.md


API Overview

new Song(options?)

const song = new Song({
  tempo: 120, // BPM (default: 120)
  timeSignature: '4/4',
  key: 'C',
  instrument: 'piano',
  title: 'My Song',
  composer: 'Me',
})

Writing Music

song.add('C4:q D4:q E4:q F4:q') // default voice
song.voice('tenor', { clef: 'treble-8' }).add('...') // named voice

Rendering

song.render('#container', {
  width: 800,
  theme: 'dark', // 'light' (default) or 'dark'
  showDynamics: true,
  showBarNumbers: true,
  showPartNames: true, // show voice labels on staves
  partNameStyle: 'abbreviated', // 'abbreviated' or 'full'
  grandStaff: false,
})

Playback

song.play() // start
song.pause() // pause
song.stop() // stop & reset
song.seekTo({ measure: 3, beat: 1 }) // jump to position
song.loop(2, 4) // loop measures 2–4
song.clearLoop() // remove loop

Export & Import

const svg = song.exportSVG() // SVG string
const midi = await song.exportMIDI() // MIDI Uint8Array
const png = await song.exportPNG() // PNG buffer (requires sharp)
const json = song.exportJSON() // portable JSON snapshot

const restored = Song.fromJSON(json) // reconstruct from JSON

Advanced

song.transpose(2)                           // shift all notes up 2 semitones
song.tempoAt(5, 80)                         // tempo change at measure 5
song.on('beat', ({ measure }) => { ... })   // playback events

Full API reference: docs/api.md


Node.js (Server-side)

import { Song } from '@bassmoses/maestro-js/node'
import fs from 'fs'

const song = new Song({ tempo: 90 })
song.add('G4:q A4:q B4:q C5:q | D5:w')

const svg = song.exportSVG()
fs.writeFileSync('sheet.svg', svg)

const png = await song.exportPNG()
fs.writeFileSync('sheet.png', png)

Plain HTML (UMD)

<script src="https://cdn.jsdelivr.net/npm/@bassmoses/maestro-js/dist/maestro.umd.js"></script>
<div id="sheet"></div>
<script>
  const song = new Maestro.Song({ tempo: 100 })
  song.add('C4 E4 G4 C5 | G4:h E4:h | C4:w')
  song.render('#sheet')
  song.play()
</script>

Dark Mode

Pass theme: 'dark' to render() — staves, notes, and text automatically use light-on-dark colors:

song.render('#container', { theme: 'dark' })

Documentation

Examples


License

MIT