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.
Maintainers
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 startto 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:
- TIS-620 - Tried first (common for Thai karaoke files)
- Windows-874 - Tried if TIS-620 fails
- 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 installBuilding Browser-Compatible Files
Before using the library in a browser, you need to build the browser-compatible files:
npm run buildThis will create browser-compatible files in the dist/ directory:
UTF8.jsMIDIEvents.jsMIDIFileHeader.jsMIDIFileTrack.jsTextEncoding.jsMIDIFile.jskarfiletis.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 startThis 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 startUsage
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:
- Copy
assets/js/midiplayer/MIDIPlayer.jsto your project - Include it in your HTML or bundle it separately
- Make sure
window.MIDIPlayeris available before usingPlayer
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 playbackpause()- Pause playbackstop()- Stop playbackgetPosition()- Get current playback position in secondssetPosition(position)- Seek to specific positionsetSoundfontEngine(engine, instance)- Set soundfont engine (SpessaSynth)openFile(fileObj)- Open MIDI file from ArrayBufferhandleFileSelect(event)- Handle file input change event
Properties:
currentPosition- Current playback positionduration- Song durationstate- Current state:'stopped','playing', or'paused'onload- Callback when song is loadedontick- Callback for position updates
KarFile
Main class for reading and parsing KAR/MIDI files.
Methods:
readFile(filePath, callback)- Read a file from diskreadBuffer(buffer)- Read from a buffergetLyrics()- 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 eventsgetEvents(type, subtype)- Get filtered eventsgetTrackEvents(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 bytesdecodeString(bytes, byteOffset, byteLength, encoding)- Decode bytes with specified encodingautoDecode(bytes, byteOffset, byteLength)- Auto-detect and decodedecodeWithFallback(bytes, byteOffset, byteLength)- Decode with fallback chain (TIS-620 → Windows-874 → UTF-8)decodeTIS620(buffer)- Decode TIS-620 encodingdecodeWindows874(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 filereadBuffer(buffer, fileName)- Read from a buffergetLyrics(filePath, callback)- Get lyrics directlygetText(filePath, callback)- Get raw text directlygetEvents(filePath, callback)- Get events directlygetInfo(filePath, callback)- Get file information
Examples
See the examples/ directory for more usage examples:
basic.js- Basic lyrics reading exampleread-buffer.js- Reading from buffer exampleget-info.js- Getting file information example
Run examples:
node examples/basic.js path/to/file.karLyrics 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.
