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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@karaplay/karaoke-player

v1.0.14

Published

Karaoke player library with A/B swapping renderer for MIDI karaoke files

Downloads

1,474

Readme

@karaplay/karaoke-player

Karaoke player library with A/B swapping renderer for MIDI karaoke files.

🎯 More Accurate Timing Options

This library now supports multiple timing methods for maximum accuracy:

  1. MIDI Note Events (⭐ Best) - Use note events from vocal track

    • Most accurate - uses native MIDI tick timing
    • Knows when to sing and when to rest (Note On/Off)
    • Word/syllable level mapping
    • See MIDI_NOTE_APPROACH.md for details
  2. MIDI Lyric Events - Use lyric events from MIDI file

  3. CUR File - Fallback method

    • Relative timing, requires normalization

Why MIDI Lyric Events?

  • Absolute Timing: No normalization needed - timing is absolute
  • Native MIDI Integration: Timing matches MIDI playback 100%
  • Tempo-Aware: Automatically handles tempo changes
  • Standard Format: Uses MIDI standard (SMF format)
  • Backward Compatible: Falls back to CUR file if MIDI doesn't have lyric events

Quick Start with MIDI Note Events (Recommended)

import { 
  parseMidiNotes, 
  notesToCharTimestamps,
  findVocalTrack 
} from '@karaplay/karaoke-player';

// Parse MIDI file for note events
const midiBuffer = await fetchArrayBuffer('/midi');
const result = parseMidiNotes(midiBuffer, {
  minNote: 48,  // C3 - vocal range
  maxNote: 84   // C6 - vocal range
});

// Find vocal track automatically
const vocalTrack = findVocalTrack(result.tracks);

// Convert note events to timestamps
const words = fullLyricText.split(/\s+/);
const timestamps = notesToCharTimestamps(
  vocalTrack.notes,
  result.tempoMap,
  result.division,
  fullLyricText,
  words
);

Alternative: MIDI Lyric Events

import { parseMidiLyrics, lyricEventsToTimestamps } from '@karaplay/karaoke-player';

const { lyricEvents, tempoMap, division } = parseMidiLyrics(midiBuffer);
const timestamps = lyricEventsToTimestamps(
  lyricEvents,
  tempoMap,
  division,
  fullLyricText
);

Installation

npm install @test-midi/karaoke-player

Usage

Basic Setup

import {
  initMidiPlayer,
  parseLyrics,
  parseCurTimestamps,
  normalizeTimestamps,
  getCurrentCharByTime,
  renderABSwap,
  computeBaseMs,
  safeGetStatus,
  playMidi,
  pauseMidi,
  stopMidi,
  seekMidi,
  setSongDuration,
  injectCalibrationUI,
  getLyricSpeed,
  getTimeOffsetMs
} from '@test-midi/karaoke-player';

// Initialize MIDI player
await initMidiPlayer();

// Set song duration
setSongDuration(180000); // 3 minutes

// Parse lyrics
const lyricText = await fetch('/lyrics.lyr').then(r => r.text());
const { fullText, lineTexts } = parseLyrics(lyricText);

// Parse CUR timestamps
const curBuffer = await fetch('/song.cur').then(r => r.arrayBuffer());
const rawTimestamps = parseCurTimestamps(curBuffer);
const timestamps = normalizeTimestamps(rawTimestamps, 180000);

// Render loop
function render() {
  const status = safeGetStatus();
  const baseMs = computeBaseMs(status);
  const lyricSpeed = getLyricSpeed();
  const timeOffsetMs = getTimeOffsetMs();
  const lyricMs = (baseMs + timeOffsetMs) * lyricSpeed;
  
  const currentCharPos = getCurrentCharByTime(timestamps, lyricMs);
  renderABSwap(currentCharPos, lineTexts, lyricMs, timestamps);
  
  requestAnimationFrame(render);
}

// Start rendering
render();

HTML Structure

Your HTML should have two elements for the karaoke lines:

<div id="line1"></div>
<div id="line2"></div>

Calibration UI

To add the calibration UI panel:

import { injectCalibrationUI } from '@test-midi/karaoke-player';

injectCalibrationUI();

This will add a floating panel for adjusting lyric speed and time offset.

API Reference

Utilities

  • $(id) - Get DOM element by ID with caching
  • esc(s) - Escape HTML special characters
  • clamp(x, a, b) - Clamp value between min and max
  • formatTime(ms) - Format milliseconds to MM:SS
  • fetchArrayBuffer(url) - Fetch array buffer from URL

Lyric Parser

  • decodeLyric(buffer) - Decode lyric buffer to text using multiple encodings
  • parseLyrics(text) - Parse lyrics text and extract singing lines
  • parseCurTimestamps(buffer) - Parse CUR timestamps from buffer
  • normalizeTimestamps(timestamps, songDurationMs) - Normalize timestamps to song duration
  • getCurrentCharByTime(timestamps, currentTimeMs) - Get current character position by time using binary search
  • findLineByCharPos(lineTexts, charPos) - Find line by character position

MIDI Player

  • initMidiPlayer() - Initialize MIDI player (requires WebAudioTinySynth)
  • setSongDuration(durationMs) - Set song duration
  • safeGetStatus() - Get player status safely
  • computeBaseMs(status) - Compute base time in milliseconds from player status
  • playMidi() - Play MIDI
  • pauseMidi() - Pause MIDI
  • stopMidi() - Stop MIDI and reset position
  • seekMidi(ms) - Seek MIDI to specific time

Calibration

  • injectCalibrationUI() - Inject calibration UI panel
  • getLyricSpeed() - Get current lyric speed
  • getTimeOffsetMs() - Get current time offset

Renderer

  • renderABSwap(currentCharPos, lineTexts, lyricMs, curTimestamps) - Render karaoke with A/B slot swapping

Requirements

  • WebAudioTinySynth library (must be loaded separately)
  • Modern browser with ES6 module support

License

MIT