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

karaoke-player

v1.1.2

Published

A Node.js library for reading and playing MIDI/KAR karaoke files with multi-encoding support (UTF-8, TIS-620, Windows-874). Includes browser-only MIDI player with Web Audio API support.

Readme

Karaoke Player - Node.js Library

A Node.js library for reading and extracting lyrics from MIDI/KAR karaoke files. This library allows you to easily read MIDI and KAR files, extract lyrics, and access MIDI event data.

Features

  • Web-based karaoke player - Run npm start to launch the web interface
  • Read MIDI and KAR files from disk or buffer
  • Extract lyrics with timing information
  • Get raw text from MIDI meta events
  • Access MIDI events and file information
  • Compatible with MIDI File type 0, 1, and 2
  • Multi-encoding support: UTF-8, TIS-620 (Thai), Windows-874/CP874 (Thai), and Latin1
  • Automatic encoding detection
  • No external dependencies for file reading (uses Node.js built-in modules)

Note: This library focuses on reading and extracting data from MIDI/KAR files. For MIDI playback, you'll need to integrate with a MIDI output library (like easymidi or midi) or a software synthesizer. The extracted MIDI events can be used to drive any MIDI playback system.

Encoding Support

The library automatically detects and handles multiple text encodings commonly used in MIDI/KAR files:

  • UTF-8 - Standard Unicode encoding
  • TIS-620 - Thai Industrial Standard 620-2533 (Thai language)
  • Windows-874 (CP874) - Windows code page 874 (Thai language with extended characters)
  • Latin1 (ISO-8859-1) - Western European encoding

Lyrics Decoding Fallback Chain

For lyrics extraction, the library uses a specific fallback chain to ensure maximum compatibility:

  1. TIS-620 - Tried first (common for Thai karaoke files)
  2. Windows-874 - Tried if TIS-620 fails
  3. UTF-8 - Final fallback for Unicode text

This fallback mechanism ensures that lyrics are correctly decoded regardless of the encoding used in the MIDI/KAR file. The order prioritizes Thai encodings since many karaoke files use them, while still supporting UTF-8 for international content.

Installation

npm install

Building Browser-Compatible Files

Before using the library in a browser, you need to build the browser-compatible files:

npm run build

This will create browser-compatible files in the dist/ directory:

  • UTF8.js
  • MIDIEvents.js
  • MIDIFileHeader.js
  • MIDIFileTrack.js
  • TextEncoding.js
  • MIDIFile.js
  • karfiletis.js

These files can be included directly in your HTML using <script> tags.

Running the Web Player

To start the web-based karaoke player interface:

npm start

This will start a local HTTP server at http://localhost:3000/ where you can:

  • Open and play MIDI/KAR files from your computer
  • View karaoke lyrics in real-time
  • Use the web-based player interface

You can change the port by setting the PORT environment variable:

PORT=8080 npm start

Usage

Basic Example - Read Lyrics

const karaoke = require('./lib');

// Read a KAR file and get lyrics
karaoke.readFile('path/to/file.kar', (err, karFile) => {
  if (err) {
    console.error('Error:', err);
    return;
  }
  
  // Get lyrics with timing
  const lyrics = karFile.getLyrics();
  lyrics.forEach(line => {
    console.log(`[${Math.round(line.time / 1000)}s] ${line.text}`);
  });
});

Read from Buffer

const fs = require('fs');
const karaoke = require('./lib');

const buffer = fs.readFileSync('path/to/file.kar');
const karFile = karaoke.readBuffer(buffer, 'filename.kar');

const lyrics = karFile.getLyrics();
console.log(lyrics);

Get File Information

karaoke.getInfo('path/to/file.kar', (err, info) => {
  if (err) {
    console.error('Error:', err);
    return;
  }
  
  console.log('Format:', info.format);
  console.log('Tracks:', info.trackCount);
  console.log('Time Division:', info.timeDivision);
});

Get Raw Text

karaoke.getText('path/to/file.kar', (err, text) => {
  if (err) {
    console.error('Error:', err);
    return;
  }
  
  // text is an object with track numbers as keys
  for (const track in text) {
    console.log(`Track ${track}:`, text[track]);
  }
});

Get MIDI Events

karaoke.getEvents('path/to/file.kar', (err, events) => {
  if (err) {
    console.error('Error:', err);
    return;
  }
  
  // events is an object with track numbers as keys
  for (const track in events) {
    console.log(`Track ${track} has ${events[track].length} events`);
  }
});

Browser Usage - Play MIDI/KAR Files in HTML

To use the Player in a browser environment, you need to include the required scripts in your HTML file.

Step 1: Include Required Scripts

First, include all the necessary library files. You can use the built files from dist/ directory or use a bundler:

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Karaoke Player</title>
</head>
<body>
  <!-- Include library scripts (order matters!) -->
  <script src="dist/UTF8.js"></script>
  <script src="dist/MIDIEvents.js"></script>
  <script src="dist/MIDIFileHeader.js"></script>
  <script src="dist/MIDIFileTrack.js"></script>
  <script src="dist/TextEncoding.js"></script>
  <script src="dist/MIDIFile.js"></script>
  <script src="assets/js/midiplayer/MIDIPlayer.js"></script>
  
  <!-- Optional: Include SpessaSynth for better sound quality -->
  <script type="importmap">
  {
    "imports": {
      "spessasynth_lib": "https://cdn.jsdelivr.net/npm/spessasynth_lib@latest/dist/index.js",
      "spessasynth_core": "https://cdn.jsdelivr.net/npm/spessasynth_core@latest/dist/index.js"
    }
  }
  </script>
  <script src="assets/js/midiplayer/SpessaSynthPlayer.js"></script>
</body>
</html>

Step 2: Create HTML Elements

<body>
  <!-- File input for selecting MIDI/KAR files -->
  <input type="file" id="fileInput" accept=".mid,.midi,.kar">
  
  <!-- Playback controls -->
  <button id="playBtn">Play</button>
  <button id="pauseBtn">Pause</button>
  <button id="stopBtn">Stop</button>
  
  <!-- Progress bar -->
  <input type="range" id="seekBar" min="0" max="100" value="0">
  
  <!-- Time display -->
  <span id="currentTime">0:00</span> / <span id="totalTime">0:00</span>
  
  <!-- Lyric display container -->
  <div id="lyricContainer"></div>
</body>

Step 3: Initialize and Use Player

<script>
  // Create player instance
  const player = new MIDIPlayer('fileInput', function(song) {
    console.log('Song loaded:', song);
    console.log('Duration:', song.duration);
    
    // Update UI when song is loaded
    document.getElementById('seekBar').max = song.duration;
    document.getElementById('totalTime').textContent = formatTime(song.duration);
    
    // Auto-play (optional)
    // player.play();
  });
  
  // Update position during playback
  player.ontick = function(song, position) {
    document.getElementById('seekBar').value = position;
    document.getElementById('currentTime').textContent = formatTime(position);
  };
  
  // Playback controls
  document.getElementById('playBtn').addEventListener('click', () => {
    player.play();
  });
  
  document.getElementById('pauseBtn').addEventListener('click', () => {
    player.pause();
  });
  
  document.getElementById('stopBtn').addEventListener('click', () => {
    player.stop();
  });
  
  // Seek functionality
  document.getElementById('seekBar').addEventListener('input', (e) => {
    const position = parseFloat(e.target.value);
    player.setPosition(position);
  });
  
  // Helper function to format time
  function formatTime(seconds) {
    const mins = Math.floor(seconds / 60);
    const secs = Math.floor(seconds % 60);
    return `${mins}:${secs.toString().padStart(2, '0')}`;
  }
</script>

Complete Example

Here's a complete working example:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Karaoke Player</title>
  <style>
    body {
      font-family: Arial, sans-serif;
      max-width: 800px;
      margin: 0 auto;
      padding: 20px;
    }
    #lyricContainer {
      margin-top: 20px;
      padding: 20px;
      background: #f0f0f0;
      border-radius: 8px;
      min-height: 200px;
      text-align: center;
      font-size: 24px;
    }
    .controls {
      display: flex;
      gap: 10px;
      margin: 20px 0;
      align-items: center;
    }
    button {
      padding: 10px 20px;
      font-size: 16px;
      cursor: pointer;
    }
    #seekBar {
      flex: 1;
    }
  </style>
</head>
<body>
  <h1>Karaoke Player</h1>
  
  <input type="file" id="fileInput" accept=".mid,.midi,.kar">
  
  <div class="controls">
    <button id="playBtn">▶ Play</button>
    <button id="pauseBtn">⏸ Pause</button>
    <button id="stopBtn">⏹ Stop</button>
    <input type="range" id="seekBar" min="0" max="100" value="0">
    <span id="timeDisplay">0:00 / 0:00</span>
  </div>
  
  <div id="lyricContainer">Select a MIDI/KAR file to start</div>
  
  <!-- Include library scripts -->
  <script src="dist/UTF8.js"></script>
  <script src="dist/MIDIEvents.js"></script>
  <script src="dist/MIDIFileHeader.js"></script>
  <script src="dist/MIDIFileTrack.js"></script>
  <script src="dist/TextEncoding.js"></script>
  <script src="dist/MIDIFile.js"></script>
  <script src="assets/js/midiplayer/MIDIPlayer.js"></script>
  
  <script>
    // Initialize player
    const player = new MIDIPlayer('fileInput', function(song) {
      console.log('Song loaded:', song);
      document.getElementById('seekBar').max = song.duration;
      updateTimeDisplay(0, song.duration);
    });
    
    // Update position
    player.ontick = function(song, position) {
      document.getElementById('seekBar').value = position;
      updateTimeDisplay(position, song.duration);
    };
    
    // Controls
    document.getElementById('playBtn').onclick = () => player.play();
    document.getElementById('pauseBtn').onclick = () => player.pause();
    document.getElementById('stopBtn').onclick = () => player.stop();
    
    document.getElementById('seekBar').addEventListener('input', (e) => {
      player.setPosition(parseFloat(e.target.value));
    });
    
    function formatTime(seconds) {
      const mins = Math.floor(seconds / 60);
      const secs = Math.floor(seconds % 60);
      return `${mins}:${secs.toString().padStart(2, '0')}`;
    }
    
    function updateTimeDisplay(current, total) {
      document.getElementById('timeDisplay').textContent = 
        `${formatTime(current)} / ${formatTime(total)}`;
    }
  </script>
</body>
</html>

Using with SpessaSynth (Better Sound Quality)

For better sound quality, you can use SpessaSynth with a SoundFont file:

<!-- Include SpessaSynth -->
<script type="importmap">
{
  "imports": {
    "spessasynth_lib": "https://cdn.jsdelivr.net/npm/spessasynth_lib@latest/dist/index.js",
    "spessasynth_core": "https://cdn.jsdelivr.net/npm/spessasynth_core@latest/dist/index.js"
  }
}
</script>
<script src="assets/js/midiplayer/SpessaSynthPlayer.js"></script>

<script>
  // Initialize SpessaSynth
  const AudioContextFunc = window.AudioContext || window.webkitAudioContext;
  const audioContext = new AudioContextFunc();
  const spessaPlayer = new SpessaSynthPlayer(audioContext);
  
  // Load SoundFont (SF2/SF3/DLS format)
  const soundfontFile = await fetch('path/to/soundfont.sf3').then(r => r.arrayBuffer());
  await spessaPlayer.initialize('path/to/spessasynth_processor.min.js');
  await spessaPlayer.loadSoundFont(soundfontFile, 'main');
  
  // Create player and set soundfont engine
  const player = new MIDIPlayer('fileInput', function(song) {
    console.log('Song loaded');
  });
  
  player.setSoundfontEngine('spessasynth', spessaPlayer);
</script>

Note:

  • The Player requires browser environment with Web Audio API support
  • All scripts must be loaded in the correct order
  • For production, consider using a bundler (webpack, rollup, etc.) instead of individual script tags

Using with Bundlers (Webpack, Rollup, Vite, etc.)

If you're using a modern bundler, you can import the library directly:

// Using ES modules
import { KarFile, MIDIFile, Player } from 'karaoke-player';

// Or using CommonJS
const { KarFile, MIDIFile, Player } = require('karaoke-player');

Important: The Player class is browser-only and requires the MIDIPlayer script to be available globally. You'll need to:

  1. Copy assets/js/midiplayer/MIDIPlayer.js to your project
  2. Include it in your HTML or bundle it separately
  3. Make sure window.MIDIPlayer is available before using Player

Example with Vite:

// main.js
import { Player } from 'karaoke-player';
import './assets/js/midiplayer/MIDIPlayer.js'; // Make MIDIPlayer available globally

// Now you can use Player
const player = new Player('fileInput', (song) => {
  console.log('Song loaded:', song);
});

Or with webpack:

// webpack.config.js
module.exports = {
  // ... other config
  plugins: [
    new webpack.ProvidePlugin({
      MIDIPlayer: path.resolve(__dirname, 'assets/js/midiplayer/MIDIPlayer.js')
    })
  ]
};

API Reference

Classes

Player (Browser-only)

Browser-only MIDI player class that uses Web Audio API for playback. Requires browser environment.

Note: This class is browser-only and will throw an error if used in Node.js. For Node.js, use KarFile and MIDIFile classes directly.

// Browser usage only
const { Player } = require('karaoke-player');

// Create player instance
const player = new Player('fileInputId', (song) => {
  console.log('Song loaded:', song);
  console.log('Duration:', song.duration);
});

// Play the loaded song
player.play();

// Pause playback
player.pause();

// Stop playback
player.stop();

// Get current position
const position = player.getPosition();

// Set position (seek)
player.setPosition(5.5); // Seek to 5.5 seconds

// Set soundfont engine (SpessaSynth)
player.setSoundfontEngine('spessasynth', spessaSynthInstance);

Methods:

  • play() - Start or resume playback
  • pause() - Pause playback
  • stop() - Stop playback
  • getPosition() - Get current playback position in seconds
  • setPosition(position) - Seek to specific position
  • setSoundfontEngine(engine, instance) - Set soundfont engine (SpessaSynth)
  • openFile(fileObj) - Open MIDI file from ArrayBuffer
  • handleFileSelect(event) - Handle file input change event

Properties:

  • currentPosition - Current playback position
  • duration - Song duration
  • state - Current state: 'stopped', 'playing', or 'paused'
  • onload - Callback when song is loaded
  • ontick - Callback for position updates

KarFile

Main class for reading and parsing KAR/MIDI files.

Methods:

  • readFile(filePath, callback) - Read a file from disk
  • readBuffer(buffer) - Read from a buffer
  • getLyrics() - Get formatted lyrics with timing (auto-detects encoding)
  • getText() - Get raw text from all tracks (auto-detects encoding)
  • readEvents() - Get all MIDI events organized by track

MIDIFile

Low-level MIDI file parser.

Methods:

  • getLyrics() - Get lyrics from meta events (auto-detects encoding)
  • getMidiEvents() - Get all MIDI events
  • getEvents(type, subtype) - Get filtered events
  • getTrackEvents(index) - Get events from a specific track

TextEncoding

Text encoding utilities for detecting and decoding various encodings.

Methods:

  • detectEncoding(bytes, byteOffset, byteLength) - Detect encoding from bytes
  • decodeString(bytes, byteOffset, byteLength, encoding) - Decode bytes with specified encoding
  • autoDecode(bytes, byteOffset, byteLength) - Auto-detect and decode
  • decodeWithFallback(bytes, byteOffset, byteLength) - Decode with fallback chain (TIS-620 → Windows-874 → UTF-8)
  • decodeTIS620(buffer) - Decode TIS-620 encoding
  • decodeWindows874(buffer) - Decode Windows-874 encoding

Supported encodings: 'utf8', 'tis620', 'windows874', 'cp874', 'latin1'

Note: The decodeWithFallback() method is used automatically for lyrics decoding in MIDI/KAR files.

Functions

  • readFile(filePath, callback) - Read a KAR/MIDI file
  • readBuffer(buffer, fileName) - Read from a buffer
  • getLyrics(filePath, callback) - Get lyrics directly
  • getText(filePath, callback) - Get raw text directly
  • getEvents(filePath, callback) - Get events directly
  • getInfo(filePath, callback) - Get file information

Examples

See the examples/ directory for more usage examples:

  • basic.js - Basic lyrics reading example
  • read-buffer.js - Reading from buffer example
  • get-info.js - Getting file information example

Run examples:

node examples/basic.js path/to/file.kar

Lyrics Format

The getLyrics() method returns an array of lyric lines with the following structure:

[
  {
    time: 0,        // Time in milliseconds
    text: "Hello",  // Lyric text
    track: 0,      // Track number
    parts: [        // Word-by-word breakdown
      { time: 0, text: "Hello" }
    ]
  },
  // ...
]

License

MIT

Original Project

This library is based on the web-based karaoke player project. The core MIDI parsing code has been adapted for Node.js use.