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

kommidi

v0.1.0

Published

A powerful and flexible TypeScript library for MIDI message processing with modular pipeline architecture

Readme

Kommidi

A powerful and flexible TypeScript library for MIDI message processing, featuring a modular pipeline architecture for real-time MIDI manipulation.

Features

  • 🎹 Web MIDI API Integration - Seamless connection to MIDI devices in the browser
  • 📁 MIDI File I/O - Parse and write standard MIDI files
  • 🔄 Pipeline Architecture - Chain multiple MIDI processors with a clean, modular API
  • 🎵 Rich Event Types - Complete support for MIDI events (Note, CC, SysEx, etc.) and Meta events
  • 🔌 Multi-port Support - Handle complex routing scenarios with named input/output ports
  • 📦 Minimal Dependencies - Only eventemitter3 and buffer
  • 🎯 TypeScript First - Full type safety and IntelliSense support

Installation

npm install kommidi

Quick Start

Basic MIDI Processing

import { WebMIDI, MIDIDevice } from 'kommidi';

// Initialize Web MIDI
const webMIDI = new WebMIDI();
await webMIDI.init();

// Create auto-selecting device (matches any available device)
const device = new MIDIDevice(webMIDI);

// Or match specific device by name pattern
// const device = new MIDIDevice(webMIDI, /Keyboard/i);

// Listen for device changes
device.on('input-changed', (port) => {
  console.log('Input device changed to:', port.name);
});

device.on('output-changed', (port) => {
  console.log('Output device changed to:', port.name);
});

// Simple echo: device input → device output
device.connect(device);

// Or with processing
import { Transposer } from 'kommidi';
const transposer = new Transposer();
transposer.transpose = 12;
device.connect(transposer).connect(device);

Reading MIDI Files

import { MIDIFileParser } from 'kommidi';
import * as fs from 'fs';

// Note: Use Uint8Array.from() to avoid Node.js Buffer pool issues
const buffer = Uint8Array.from(fs.readFileSync('song.mid'));
const midiFile = MIDIFileParser.parse(buffer);

console.log(`Tempo: ${midiFile.ticksPerBeat} ticks per beat`);
console.log(`Tracks: ${midiFile.tracks.length}`);

midiFile.tracks.forEach((track, i) => {
  console.log(`Track ${i}: ${track.name} (${track.length} events)`);
});

Playing MIDI Files

import { MIDIFileParser, MIDIPlayer, ConsoleOutput } from 'kommidi';
import * as fs from 'fs';

// Load and parse MIDI file
const buffer = Uint8Array.from(fs.readFileSync('song.mid'));
const midiFile = MIDIFileParser.parse(buffer);

// Create player and output
const player = new MIDIPlayer(midiFile);
const output = new ConsoleOutput();

player.connect(output);

// Control playback
player.play();
player.pause();
player.seek(480);  // Seek to tick 480
player.stop();

// Events
player.on('end', () => console.log('Playback finished'));
player.on('loop', () => console.log('Looping...'));

// Properties
console.log(player.currentTick, player.currentTimeMs);
console.log(player.durationTicks, player.durationMs);
console.log(player.progress);  // 0-1

Recording MIDI

import { MIDIRecorder, MIDIFileWriter } from 'kommidi';
import * as fs from 'fs';

const recorder = new MIDIRecorder();
recorder.bpm = 120;
recorder.ticksPerBeat = 480;

// Connect input to recorder
midiInput.connect(recorder);

// Start recording
recorder.start();

// ... play some notes ...

// Stop and get MIDI file
const midiFile = recorder.stop();

// Save to file
const bytes = MIDIFileWriter.write(midiFile);
fs.writeFileSync('recorded.mid', bytes);

Creating Custom MIDI Events

import { NoteOn, NoteOff, ControlChange, ControlType } from 'kommidi';

// Create a Note On event
const noteOn = new NoteOn({
  channel: 0,
  pitch: 60,      // Middle C
  velocity: 100
});

// Create a Note Off event
const noteOff = new NoteOff({
  channel: 0,
  pitch: 60,
  offVel: 0
});

// Create a Control Change event
const cc = new ControlChange({
  channel: 0,
  control: ControlType.HoldPedal,
  value: 127
});

// Get raw MIDI bytes
console.log(noteOn.bytes); // Uint8Array [144, 60, 100]

Core Concepts

Web MIDI Device Management

Kommidi provides flexible device management with two approaches:

1. Automatic Device Selection (Recommended)

Use MIDIDevice for automatic device selection based on patterns:

const webMIDI = new WebMIDI();
await webMIDI.init();

// Auto-select any device with "Keyboard" in the name
const device = new MIDIDevice(webMIDI, /Keyboard/i);

// The device starts with dummy ports and automatically switches
// to matching real devices when they connect
console.log(device.hasRealInput);  // false initially
console.log(device.inputPort.name); // "(No Input)"

// When "USB Keyboard" connects:
// → device.hasRealInput becomes true
// → device.inputPort.name becomes "USB Keyboard"
// → 'input-changed' event is emitted

device.on('input-changed', (port) => {
  if (device.hasRealInput) {
    console.log('Real device connected:', port.name);
  } else {
    console.log('Using dummy device');
  }
});

2. Manual Port Selection

For more control, work directly with ports:

const webMIDI = new WebMIDI();
await webMIDI.init();

// List available ports
const inputs = webMIDI.getInputPorts();
const outputs = webMIDI.getOutputPorts();

console.log('Inputs:', inputs.map(p => p.name));
console.log('Outputs:', outputs.map(p => p.name));

// Get specific port
const myInput = webMIDI.getInputById('port-id-123');

// Listen for port changes
webMIDI.on('input-ports-changed', (inputs) => {
  updateDeviceDropdown(inputs);
});

// Create custom device pairing
const customDevice = new MIDIDevice(webMIDI);
// Then manually control which ports to use in your application

Pipeline Architecture

Kommidi uses a pipeline-based architecture where MIDI events flow through connected nodes:

[MIDI Input] → [Processor 1] → [Processor 2] → [MIDI Output]

Node Types

  • MIDISource - Outputs MIDI events (e.g., InputPort, MIDI file player)
  • MIDITarget - Receives MIDI events (e.g., OutputPort, MIDI file recorder)
  • MIDIIONode - Both receives and outputs (e.g., Transposer, effects, routers)

Building a Processing Chain

import { WebMIDI, MIDIDevice, Transposer } from 'kommidi';

const webMIDI = new WebMIDI();
await webMIDI.init();

// Auto-select device by pattern
const device = new MIDIDevice(webMIDI, /My MIDI Device/i);

// Create processors
const transposer = new Transposer();
transposer.transpose = 7; // Perfect fifth up

// Chain them together
device
  .connect(transposer)
  .connect(device); // Loop back to output

// Or connect to multiple targets
const output1 = new MIDIDevice(webMIDI, /Output 1/i);
const output2 = new MIDIDevice(webMIDI, /Output 2/i);
transposer.connect(output1);
transposer.connect(output2);

API Overview

Core Events

import { 
  NoteOn, NoteOff,           // Note events
  ControlChange,              // Control Change
  ProgramChange,              // Program Change
  PitchWheel,                 // Pitch Bend
  AfterTouch,                 // Polyphonic Key Pressure
  ChannelPressure,            // Channel Pressure
  SysEx,                      // System Exclusive
  Clock, Start, Stop,         // Timing messages
} from 'kommidi';

Meta Events (MIDI Files)

import {
  Tempo,                      // Set tempo
  TimeSignature,              // Time signature
  KeySignature,               // Key signature
  TrackName,                  // Track name
  Lyric, Marker, Cue,        // Text events
  EndOfTrack,                 // End of track marker
} from 'kommidi/core/MetaEvent';

// Tempo — recommended: use fromBpm() helper
const tempo = Tempo.fromBpm(120);
tempo.bpm;                         // 120
tempo.microSecondsPerQuarterNote;  // 500000

// Time Signature: 3/4 time
// Args: numerator, log2(denominator), midiClocksPerClick, 32ndNotesPerQuarter
// Common: 4/4 = (4, 2, 24, 8), 3/4 = (3, 2, 24, 8), 6/8 = (6, 3, 24, 8)
const timeSig = new TimeSignature(3, 2, 24, 8);

// Key Signature: D major (2 sharps)
// key: -7 (7 flats) to 7 (7 sharps), 0 = C
// scale: 0 = Major, 1 = Minor
const keySig = new KeySignature(2, 0);

I/O

import { 
  WebMIDI, 
  MIDIDevice, 
  MIDIFileParser, 
  MIDIFileWriter 
} from 'kommidi';

// Web MIDI
const webMIDI = new WebMIDI();
await webMIDI.init({ sysex: true });

// Get available ports
const inputs = webMIDI.getInputPorts();
const outputs = webMIDI.getOutputPorts();

// Auto-select device by pattern
const device = new MIDIDevice(webMIDI, /Keyboard/i);

// Or match any device
const anyDevice = new MIDIDevice(webMIDI);

// Check device status
console.log(device.hasRealInput);  // true if using real device
console.log(device.hasRealOutput); // false if using dummy device

// MIDI Files - Parse
const midiFile = MIDIFileParser.parse(buffer);

// MIDI Files - Write
const bytes = MIDIFileWriter.write(midiFile);

Processing Modules

import { Transposer, ConsoleOutput } from 'kommidi';

// Transpose notes
const transposer = new Transposer();
transposer.transpose = 12; // Shift by 12 semitones

// Debug output (logs all events to console)
const consoleOutput = new ConsoleOutput();
midiInput.connect(consoleOutput);

Playback & Recording

import { MIDIPlayer, MIDIRecorder } from 'kommidi';

// Play MIDI files with proper timing
const player = new MIDIPlayer(midiFile);
player.loop = true;
player.playbackRate = 1.5;  // 1.5x speed
player.play();

// Playback control
player.pause();
player.seek(480);            // Seek to tick position
player.seekToTime(5000);     // Seek to 5 seconds
player.stop();

// State and position
player.state;                // 'stopped' | 'playing' | 'paused'
player.isPlaying;            // boolean
player.currentTick;          // current position in ticks
player.currentTimeMs;        // current position in ms
player.durationTicks;        // total duration in ticks
player.durationMs;           // total duration in ms
player.progress;             // 0-1

// Events
player.on('play', () => {});
player.on('pause', () => {});
player.on('stop', () => {});
player.on('end', () => {});
player.on('loop', () => {});
player.on('seek', ({ tick, timeMs }) => {});
player.on('load', ({ durationTicks, durationMs }) => {});
player.on('ratechange', (rate) => {});

// Record MIDI events
const recorder = new MIDIRecorder();
recorder.bpm = 120;
recorder.ticksPerBeat = 480;
recorder.start();
// ... play some notes ...
const midiFile = recorder.stop();  // Returns MIDIFile

recorder.on('recordingStart', () => {});
recorder.on('recordingStop', () => {});

Data Models

import { MIDIFile, MIDIFileParser, MIDIFileWriter, Sequencer } from 'kommidi';

// MIDIFile: event-based representation (delta times)
const midiFile = new MIDIFile({ ticksPerBeat: 480 });

// Sequencer: note-based representation (easier to work with)
const sequencer = Sequencer.fromMIDIFile(midiFile);

// Inspect tracks and notes
for (const track of sequencer.tracks) {
  console.log(track.name, track.notes.length);
  for (const note of track.notes) {
    // { pitch, start, duration, velocity?, channel? }
  }
}

// Access tempo and signature data
sequencer.tempoMap;              // [{ time: 0, bpm: 120 }]
sequencer.timeSignatureChanges;  // [{ time: 0, numerator: 4, denominator: 4 }]
sequencer.keySignatureChanges;   // [{ time: 0, key: 0, scale: 0 }]

// Programmatically create music
const seq = new Sequencer();
seq.tempoMap.push({ time: 0, bpm: 140 });
seq.addTrack({
  name: 'Piano',
  defaultChannel: 0,
  notes: [
    { pitch: 60, start: 0, duration: 480, velocity: 100 },
    { pitch: 64, start: 480, duration: 480, velocity: 90 },
  ]
});

// Convert back to MIDIFile and save
const bytes = MIDIFileWriter.write(seq.toMIDIFile());

For complete API signatures, type definitions, common pitfalls, and MIDI reference values, see docs/API.md. For architecture and design details, see docs/ARCHITECTURE.md.

Advanced Usage

Creating Custom Processors

import { MIDIIONode, BaseEvent, NoteOn, EventParser } from 'kommidi';

class VelocityScaler extends MIDIIONode {
  scale = 1.0;

  feed(event: BaseEvent): void {
    if (event instanceof NoteOn) {
      // Clone to avoid mutating original event
      const cloned = EventParser.cloneEvent(event) as NoteOn;
      cloned.velocity = Math.min(127, Math.floor(cloned.velocity * this.scale));
      this.output(cloned);
    } else {
      this.output(event);
    }
  }
}

// Usage
const scaler = new VelocityScaler();
scaler.scale = 0.8; // Reduce velocity by 20%
input.connect(scaler).connect(output);

Event Filtering

import { MIDIIONode, BaseEvent, NoteOn, NoteOff } from 'kommidi';

class NoteFilter extends MIDIIONode {
  feed(event: BaseEvent): void {
    // Only pass through note events
    if (event instanceof NoteOn || event instanceof NoteOff) {
      this.output(event);
    }
  }
}

Event Monitoring

// Listen to events without interrupting the pipeline
device.on('output', (event: BaseEvent) => {
  console.log('MIDI event:', event);
});

Project Structure

kommidi/
├── src/
│   ├── core/              # Core event types and base classes
│   │   ├── MIDIEvent.ts   # MIDI message types
│   │   ├── MetaEvent.ts   # MIDI file meta events (Tempo, TimeSignature, etc.)
│   │   ├── EventParser.ts # Binary MIDI parser
│   │   ├── Module.ts      # Base classes (MIDISource, MIDITarget, MIDIIONode)
│   │   └── VarInt.ts      # Variable-length integer encoding
│   ├── model/             # Data models
│   │   ├── MIDIFile.ts    # MIDIFile and MIDITrack classes (event-based)
│   │   └── Sequencer.ts   # Sequencer class (note-based data structure)
│   ├── io/                # Input/Output
│   │   ├── WebMIDI.ts     # Web MIDI API wrapper (port management)
│   │   ├── MIDIDevice.ts  # Auto-selecting device manager
│   │   ├── DummyPorts.ts  # Placeholder ports (no-op implementations)
│   │   └── MIDIFileIO.ts  # MIDI file parser and writer
│   ├── module/            # Built-in processors
│   │   ├── Transposer.ts  # Pitch transposition
│   │   └── ConsoleOutput.ts # Debug output to console
│   └── playback/          # Playback and recording
│       ├── MIDIPlayer.ts  # MIDI file playback with timing
│       └── MIDIRecorder.ts # MIDI recording to file
└── dist/                  # Compiled output

Architecture

Design Principles

  1. Immutability - Events are cloned before modification to prevent side effects
  2. Type Safety - Full TypeScript support with strict typing
  3. Modularity - Small, composable processors
  4. Performance - Minimal overhead, efficient event routing
  5. Extensibility - Easy to create custom processors

Key Patterns

  • Pipeline Pattern - Chain processors for complex transformations
  • Observer Pattern - EventEmitter for monitoring without side effects
  • Factory Pattern - EventParser creates appropriate event types

Development & Build

Development

Start the development server with hot reload:

npm run dev

Open http://localhost:5173 to see the interactive demo page with examples.

Testing

npm test          # Run tests in watch mode
npm run test:run  # Run tests once
npm run test:ui   # Run tests with browser UI

Building

Build the library for production:

npm run build

This generates:

  • dist/*.es.js - ES modules (tree-shakeable, multiple entry points)
  • dist/kommidi.umd.js - UMD bundle (single file for <script> tag)
  • dist/*.d.ts - TypeScript declarations
  • Source maps for debugging

Build scripts:

  • npm run build - Build ES + UMD
  • npm run build:es - Build ES modules only
  • npm run build:umd - Build UMD bundle only

Publishing

The library is ready to publish to npm:

npm run build
npm publish

The prepublishOnly script automatically builds before publishing.

Import Paths

Kommidi supports two import strategies:

Strategy 1: Main Entry Point (Recommended for most users)

Import everything from the main entry point. Modern bundlers will tree-shake unused code automatically.

import { 
  WebMIDI, 
  MIDIDevice,
  NoteOn, 
  NoteOff,
  Transposer,
  MIDIFileParser,
  MIDIPlayer,
  Sequencer 
} from 'kommidi';

Strategy 2: Subpath Imports (For granular control)

Import from specific modules for explicit control over dependencies.

// Core types and base classes
import { NoteOn, NoteOff, MIDIIONode } from 'kommidi/core';
import { Tempo, TimeSignature } from 'kommidi/core/MetaEvent';

// Data models
import { MIDIFile } from 'kommidi/model/MIDIFile';
import { Sequencer } from 'kommidi/model/Sequencer';

// I/O operations
import { WebMIDI, MIDIDevice } from 'kommidi/io';
import { MIDIFileParser, MIDIFileWriter } from 'kommidi/io/MIDIFileIO';

// Processing modules
import { Transposer, ConsoleOutput } from 'kommidi/module';

// Playback and recording
import { MIDIPlayer } from 'kommidi/playback/MIDIPlayer';
import { MIDIRecorder } from 'kommidi/playback/MIDIRecorder';

Both strategies are fully supported. Choose based on your preference:

  • Use Strategy 1 for simplicity and convenience
  • Use Strategy 2 for explicit dependency management in large projects

Browser Compatibility

  • Chrome/Edge 43+ (Web MIDI API support)
  • Firefox 108+ (with dom.webmidi.enabled flag)
  • Safari 17+ (requires user permission)

Node.js Usage

MIDI file I/O, Sequencer, and MIDIPlayer work in Node.js without any polyfill:

import { MIDIFileParser, MIDIFileWriter, Sequencer } from 'kommidi';
import * as fs from 'fs';

// Important: Use Uint8Array.from() to avoid Node.js Buffer pool issues
const buffer = Uint8Array.from(fs.readFileSync('song.mid'));
const midiFile = MIDIFileParser.parse(buffer);
const sequencer = Sequencer.fromMIDIFile(midiFile);

Web MIDI device access (WebMIDI, MIDIDevice) requires a browser environment. MIDIPlayer depends on performance.now() and setTimeout, available in Node.js 16+.

Development Status

  • ✅ Core MIDI events
  • ✅ Meta events (with Tempo.fromBpm() helper)
  • ✅ Web MIDI integration
  • ✅ MIDI file parsing and writing
  • ✅ MIDI file playback (MIDIPlayer with play/pause/stop/seek/loop)
  • ✅ MIDI recording (MIDIRecorder)
  • ✅ Basic processors (Transposer, ConsoleOutput)
  • ✅ Sequencer (note-based data structure)
  • 📋 More built-in processors (planned)

Contributing

This project follows these coding guidelines:

  • Prefer functional, immutable style
  • Use early returns to reduce nesting
  • Add JSDoc comments to public APIs
  • Write descriptive variable names
  • Keep functions focused and small

License

MIT

Related Projects

Examples

See the examples/ directory for interactive demos and scripts.

Web (Browser)

Run npm run dev and open http://localhost:5173 to try the interactive demo page:

  • Echo / Passthrough — Route MIDI input directly to output
  • MIDI Player — Load and play Standard MIDI Files
  • MIDI Recorder — Record MIDI input and save as .mid
  • Transpose — Shift note pitches by semitones
  • Velocity Scaler — Scale note velocities (loudness)
  • Event Filter — Filter specific MIDI event types

Node.js Scripts

# Parse and inspect a MIDI file
tsx examples/scripts/06-read-midi-file.ts path/to/file.mid

# Play a MIDI file with console output
tsx examples/scripts/test-player.ts path/to/file.mid

# Play with looping
tsx examples/scripts/test-player.ts path/to/file.mid --loop

See examples/README.md for full details and how to create your own examples.


Made with ❤️ for musicians and developers