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

@drop-ai/core

v0.3.3

Published

Headless DAW engine — domain models, command system, audio engine, automation, plugins, MIDI

Readme

@drop-ai/core

Headless DAW (Digital Audio Workstation) engine for TypeScript/JavaScript. Provides domain models, command system, audio engine, automation, plugins, and MIDI — all with zero browser or framework dependencies.

npm install @drop-ai/core

Features

  • Headless — No browser, no React, no framework required. Runs in Node.js, Electron, or any JS runtime.
  • Command Pattern — All mutations go through CommandExecutor with built-in Undo/Redo.
  • Dependency Injection — Bring your own audio backend via the AudioProvider interface.
  • 60+ Commands — Transport, tracks, regions, automation, plugins, MIDI, markers, ranges, export.
  • 20+ Built-in Plugins — EQ, compressor, reverb, delay, saturation, de-esser, and more.
  • Signal-based Events — Type-safe reactive event system (Qt Signal/Slot pattern).
  • Serialization — Full session save/load with JSON snapshots.
  • TypeScript-first — Complete type definitions included.

Quick Start

import {
  AudioEngine,
  Session,
  Track,
  TrackType,
  CommandExecutor,
  CommandType,
} from '@drop-ai/core';

// 1. Implement AudioProvider interface for your platform
class MyAudioBackend implements AudioProvider {
  // ... implement the interface methods
}

// 2. Initialize the engine with your backend
const engine = AudioEngine.getInstance(new MyAudioBackend());

// 3. Work with the session directly
const track = engine.session.addTrack('Vocals', TrackType.AUDIO);

// 4. Or use the command system (with undo/redo support)
await CommandExecutor.getInstance().execute({
  type: CommandType.ADD_TRACK,
  payload: { name: 'Guitar', trackType: 'audio' },
});

Architecture

┌──────────────────────────────────────────────────┐
│  Your App                                        │
│  (React, Electron, Node.js CLI, etc.)            │
│                                                  │
│  ┌─────────────┐  ┌────────────────────────────┐ │
│  │ UI Layer    │  │ AudioProvider (impl)        │ │
│  │ (optional)  │  │ e.g. Web Audio, PortAudio  │ │
│  └──────┬──────┘  └─────────────┬──────────────┘ │
├─────────┼───────────────────────┼────────────────┤
│         ▼                       ▼                │
│  ┌─────────────┐  ┌────────────────────────────┐ │
│  │ Command     │  │ AudioEngine                │ │
│  │ Executor    │  │ (session + backend)        │ │
│  └──────┬──────┘  └────────────────────────────┘ │
│         ▼                                        │
│  ┌───────────────────────────────────────┐       │
│  │ Domain Layer                          │       │
│  │ Session → Track → Playlist → Region   │       │
│  │ Automation, Plugins, MIDI, Markers    │       │
│  └───────────────────────────────────────┘       │
│                                                  │
│  @drop-ai/core                                        │
└──────────────────────────────────────────────────┘

Core Concepts

Session

The root container for a DAW project. Manages tracks, sources, markers, ranges, tempo, and transport state.

import { Session, TrackType } from '@drop-ai/core';

const session = new Session('My Song', undefined, 44100);

// Tracks
const vocal = session.addTrack('Vocals', TrackType.AUDIO);
const bass = session.addTrack('Bass', TrackType.AUDIO);
const synth = session.addTrack('Synth', TrackType.MIDI);
const reverb = session.addAuxTrack('Reverb Bus');

// Transport
session.setTempo(120);
session.setTimeSignature(4, 4);

// Markers
session.addMarker('Chorus', 44100 * 30); // at 30 seconds

// Ranges
const loopRange = session.addRange('Loop A', 44100 * 10, 44100 * 20);
session.setLoopRange(loopRange.id);
session.setLoopEnabled(true);

// Serialization
const snapshot = session.toJSON();
const restored = Session.fromJSON(snapshot);

Track

Represents an audio, MIDI, aux, bus, folder, or VCA track. Each track has a Route (signal chain) and a Playlist (region container).

const track = session.addTrack('Lead Guitar', TrackType.AUDIO);

track.setMute(false);
track.setSolo(true);
track.setArmed(true);
track.setColor('#ff6b6b');
track.setMonitorMode(MonitorMode.AUTO);

// React to changes
track.muteChanged.connect((muted) => console.log('Mute:', muted));
track.soloChanged.connect((soloed) => console.log('Solo:', soloed));

Track Types:

| Type | Description | |------|-------------| | AUDIO | Standard audio track with region playback | | MIDI | MIDI track with note data and virtual instruments | | AUX | Auxiliary bus for send effects (reverb, delay) | | BUS | Mix bus for subgroup routing | | FOLDER | Folder track for organizing child tracks | | VCA | VCA fader for linked level control |

Region

A segment of audio on the timeline, referencing a Source.

import { Region } from '@drop-ai/core';

const region = new Region(
  'region-1',    // id
  'source-1',    // sourceId
  0,             // start (frames)
  44100 * 10,    // length (10 seconds at 44.1kHz)
  0,             // sourceStart
  'Vocal Take 1' // name
);

region.setFadeIn(4410);           // 100ms fade in
region.setFadeOut(4410);          // 100ms fade out
region.move(44100 * 5);           // move to 5 seconds
region.resize(44100 * 8);         // resize to 8 seconds

AudioEngine

Central controller connecting the session to an audio backend. Uses the singleton pattern with dependency injection.

import { AudioEngine, AudioProvider } from '@drop-ai/core';

// Initialize with your backend implementation
const engine = AudioEngine.getInstance(myBackend);
await engine.initialize();

// Transport
await engine.start();
engine.pause();
engine.seek(10.5); // seek to 10.5 seconds
engine.stop();

// Metering
const meter = engine.getMeterData('track-1');
console.log('Peak:', meter.peak, 'RMS:', meter.rms);

// Export
await engine.exportAudio(engine.getExportConfig(), engine.getExportStatus());

// Swap backend at runtime
engine.setBackend(new DifferentBackend());

AudioProvider Interface

Implement this interface to connect @drop-ai/core to any audio system.

import { AudioProvider } from '@drop-ai/core';

class WebAudioBackend implements AudioProvider {
  async initialize(): Promise<void> { /* ... */ }

  // Transport
  start(): void { /* ... */ }
  stop(): void { /* ... */ }
  pause(): void { /* ... */ }
  seek(time: number): void { /* ... */ }

  // Track management
  createTrack(trackId, name, inputId, outputId): void { /* ... */ }
  deleteTrack(trackId): void { /* ... */ }

  // Region scheduling
  scheduleRegion(trackId, region): void { /* ... */ }
  updateRegions(trackId, regions): void { /* ... */ }
  removeRegion(trackId, regionId): void { /* ... */ }

  // Metering
  getMeterData(trackId): MeterData { /* ... */ }
  getMasterMeterData(): MeterData { /* ... */ }

  // ... and more (see full interface in source)
}

Included backend examples (in the drop.ai app, not in this package):

| Backend | Environment | Description | |---------|-------------|-------------| | ToneAudioProvider | Browser | Tone.js + Web Audio API | | HeadlessAudioProvider | Node.js | No-op backend for CLI/testing | | MockAudioProvider | Test | Mock backend for unit tests |


Command System

All state mutations can go through the command system, providing validation (via Zod), handler routing, and undo/redo support.

Executing Commands

import { CommandExecutor, CommandType } from '@drop-ai/core';

const executor = CommandExecutor.getInstance();

// Add a track
await executor.execute({
  type: CommandType.ADD_TRACK,
  payload: { name: 'Vocals', trackType: 'audio' },
});

// Set volume
await executor.execute({
  type: CommandType.SET_VOLUME,
  payload: { trackId: 'track-1', volume: 0.75 },
});

// Split region at playhead
await executor.execute({
  type: CommandType.SPLIT_AT_PLAYHEAD,
  payload: { trackId: 'track-1', frame: 44100 * 15 },
});

// Add a plugin
await executor.execute({
  type: CommandType.ADD_PLUGIN,
  payload: { trackId: 'track-1', pluginId: 'internal-eq6' },
});

Undo / Redo

const history = executor.history;

await history.undo();
await history.redo();
await history.undoMultiple(3); // undo 3 steps

console.log(history.canUndo);       // true
console.log(history.nextUndoLabel);  // "Add Track"

// Transaction: group multiple commands into one undo step
history.beginTransaction('Move and resize');
// ... execute multiple commands ...
await history.commitTransaction();

Registering Custom Handlers

import { CommandHandler, CommandResult } from '@drop-ai/core';

class MyCustomHandler implements CommandHandler {
  readonly handledTypes = ['MY_CUSTOM_COMMAND'];

  async execute(type: string, payload: any): Promise<CommandResult> {
    // your logic here
    return { success: true };
  }
}

CommandExecutor.getInstance().registerHandler(new MyCustomHandler());

Command Types

Transport: PLAY, PAUSE, STOP, SEEK, SET_TEMPO, SET_TIME_SIGNATURE, TOGGLE_METRONOME, START_RECORDING, STOP_RECORDING

Tracks: ADD_TRACK, REMOVE_TRACK, SET_VOLUME, SET_PAN, MUTE_TRACK, SOLO_TRACK, ARM_TRACK, SET_TRACK_MONITOR

Regions: ADD_REGION, REMOVE_REGION, MOVE_REGION, RESIZE_REGION, SPLIT_AT_PLAYHEAD, DUPLICATE_REGION, COPY_REGION, PASTE_REGION, MERGE_REGIONS, TRIM_REGION, SET_REGION_FADES, REVERSE_REGION, NORMALIZE_REGION, STRIP_SILENCE, TIME_STRETCH_REGION, LOCK_REGION, GROUP_REGIONS, UNGROUP_REGIONS

Automation: ADD_AUTOMATION, MOVE_AUTOMATION_POINT, REMOVE_AUTOMATION_POINT

Plugins: ADD_PLUGIN, REMOVE_PLUGIN, SET_PLUGIN_PARAMETER

Ranges: ADD_RANGE, REMOVE_RANGE, SET_RANGE, SET_LOOP_RANGE, SET_PUNCH_RANGE, TOGGLE_LOOP

Markers: ADD_MARKER, REMOVE_MARKER, MOVE_MARKER

IO/Routing: CONNECT_IO, DISCONNECT_IO, ADD_SEND_BUS, REMOVE_SEND_BUS, SET_SEND_LEVEL

Session: EXPORT, NEW_SESSION, LOAD_SESSION, SAVE_SESSION, SAVE_SNAPSHOT

History: UNDO, REDO, SELECTION_UNDO, SELECTION_REDO


Plugins

20+ built-in audio effect plugins, managed by PluginManager.

import { PluginManager } from '@drop-ai/core';

const manager = PluginManager.getInstance();

// List all available plugins
const plugins = manager.getAvailablePlugins();
plugins.forEach(p => console.log(p.id, p.name));

// Create a plugin instance
const eq = manager.createPlugin('internal-eq6');
eq.setParameter('band1-freq', 100);
eq.setParameter('band1-gain', 3.0);

// Get/set full state (presets)
const state = eq.getState();
eq.setState(state);

// React to parameter changes
eq.parameterChanged.connect(({ id, value }) => {
  console.log(`${id} changed to ${value}`);
});

Available Plugins

| ID | Name | Description | |----|------|-------------| | internal-eq6 | Parametric EQ | 6-band parametric equalizer | | internal-compressor | Compressor | Dynamics compressor | | internal-multiband-comp | Multiband Compressor | Multi-band dynamics | | internal-expander | Expander | Expander/gate | | internal-gate | Gate | Noise gate | | internal-deesser | De-Esser | Sibilance reduction | | internal-reverb | Reverb | Algorithmic reverb | | internal-convolver | Convolution Reverb | IR-based reverb | | internal-delay | Delay | Simple delay | | internal-sync-delay | Sync Delay | BPM-synced delay | | internal-chorus | Chorus | Chorus effect | | internal-phaser | Phaser | Phaser effect | | internal-tremolo | Tremolo | Tremolo effect | | internal-vibrato | Vibrato | Vibrato effect | | internal-autopan | Auto Pan | Automatic panning | | internal-distortion | Distortion | Distortion/overdrive | | internal-tape-sat | Tape Saturation | Analog tape emulation | | internal-filter | Filter | Multi-mode filter | | internal-eq3 | 3-Band EQ | Simple 3-band EQ | | internal-gain | Gain | Utility gain |


Automation

Per-parameter automation with multiple recording modes.

import { AutomationList, AutomationMode } from '@drop-ai/core';

const automation = new AutomationList();

// Add points
automation.addPoint(0, 0.5);       // value 0.5 at time 0
automation.addPoint(2.0, 1.0);     // value 1.0 at time 2s
automation.addPoint(4.0, 0.0);     // value 0.0 at time 4s

// Read interpolated value
const value = automation.getValueAt(1.0); // ~0.75 (linear interpolation)

// Modes
automation.mode = AutomationMode.READ;   // playback only
automation.mode = AutomationMode.WRITE;  // overwrite
automation.mode = AutomationMode.TOUCH;  // write while touching, snap back on release
automation.mode = AutomationMode.LATCH;  // write while touching, hold last value

// Range operations
const copied = automation.copy(1.0, 3.0);
automation.paste(copied, 5.0);
automation.eraseRange(1.0, 3.0);

Signal (Event System)

Type-safe event emitter inspired by Qt's Signal/Slot pattern. Used throughout the domain layer.

import { Signal } from '@drop-ai/core';

const signal = new Signal<number>();

// Subscribe
const sub = signal.connect((value) => {
  console.log('Received:', value);
});

// Emit
signal.emit(42); // logs "Received: 42"

// Unsubscribe
sub.dispose();

// Or disconnect by reference
const handler = (v: number) => console.log(v);
signal.connect(handler);
signal.disconnect(handler);

// Clear all listeners
signal.clear();

Common Signals

// Session
session.trackAdded.connect((track) => { /* ... */ });
session.trackRemoved.connect((trackId) => { /* ... */ });
session.tempoChanged.connect((bpm) => { /* ... */ });
session.playingChanged.connect((isPlaying) => { /* ... */ });
session.selectionChanged.connect((selectedIds) => { /* ... */ });
session.markerAdded.connect((marker) => { /* ... */ });

// Track
track.muteChanged.connect((muted) => { /* ... */ });
track.soloChanged.connect((soloed) => { /* ... */ });
track.armChanged.connect((armed) => { /* ... */ });

// CommandExecutor
executor.commandExecuted.connect(({ type, payload }) => { /* ... */ });

// CommandHistory
history.historyChanged.connect(() => { /* ... */ });

Actions & Key Bindings

Map keyboard shortcuts to commands.

import { ActionRegistry, ActionCategory } from '@drop-ai/core';

const registry = ActionRegistry.getInstance();

// Register default actions (typically done at app bootstrap)
registry.registerDefaults([
  {
    id: 'transport.play',
    label: 'Play/Pause',
    category: ActionCategory.TRANSPORT,
    defaultKey: 'Space',
    commandFactory: () => ({ type: CommandType.PLAY, payload: {} }),
  },
  {
    id: 'edit.undo',
    label: 'Undo',
    category: ActionCategory.EDIT,
    defaultKey: 'Ctrl+Z',
    commandFactory: () => ({ type: CommandType.UNDO, payload: {} }),
  },
]);

// Execute by action ID
await registry.execute('transport.play');

// Query
const actions = registry.getActionsByCategory();
const key = registry.getEffectiveKey('transport.play'); // 'Space'

Custom Key Bindings

import { KeyBindings } from '@drop-ai/core';

const bindings = KeyBindings.getInstance();

bindings.setBinding('transport.play', 'Enter');
bindings.getBinding('transport.play'); // 'Enter'
bindings.removeBinding('transport.play'); // back to default
bindings.resetToDefaults();

Preferences

Persistent settings with defaults.

import { Preferences } from '@drop-ai/core';

const prefs = Preferences.getInstance();

prefs.set('sampleRate', 48000);
prefs.set('audioBufferSize', 256);
prefs.set('theme', 'dark');
prefs.set('snapToGrid', true);
prefs.set('historyDepth', 100);

const sr = prefs.get('sampleRate'); // 48000

// React to changes
prefs.preferenceChanged.connect(() => {
  console.log('Settings updated');
});

// Reset
prefs.resetToDefaults();

Available Keys: audioBufferSize, sampleRate, theme, autoSaveInterval, snapToGrid, gridSubdivision, meterType, showMinimap, followPlayhead, countInBars, historyDepth, saveHistory, saveHistoryDepth


Storage

Session persistence via IndexedDB (browser) or localStorage fallback.

import { SessionStorage } from '@drop-ai/core';

const storage = SessionStorage.getInstance();

// Save
await storage.saveSession(session);

// List
const sessions = await storage.listSessions();
// [{ id: '...', name: 'My Song', modified: Date }]

// Load
const snapshot = await storage.loadSession(sessions[0].id);
const restored = Session.fromJSON(snapshot);

// Snapshots (named save points)
const snapId = await storage.saveSnapshot(session.id, 'Before mixing', session.toJSON());
const snap = await storage.loadSnapshot(snapId);

// Delete
await storage.deleteSession(session.id);

Processing Chain

Each track has a Route containing an ordered chain of processors.

import {
  GainProcessor,
  PanProcessor,
  PluginInsert,
  SendProcessor,
  MeterProcessor,
} from '@drop-ai/core';

// Processor types available in the chain:
// GainProcessor  — Fader / Trim level control
// PanProcessor   — Stereo panning and width
// PluginInsert   — Plugin effect slot
// SendProcessor  — Pre/Post fader send to aux bus
// MeterProcessor — Peak/RMS metering

Usage Examples

Node.js CLI

import { AudioEngine, Session, CommandExecutor, CommandType } from '@drop-ai/core';

// Headless backend (no audio output)
class HeadlessBackend implements AudioProvider {
  async initialize() {}
  start() {}
  stop() {}
  // ... no-op implementations
}

const engine = AudioEngine.getInstance(new HeadlessBackend());
const session = engine.session;

session.addTrack('Track 1', TrackType.AUDIO);
console.log('Tracks:', session.tracks.map(t => t.name));

const snapshot = session.toJSON();
// Save to file, process offline, etc.

Electron App

import { AudioEngine, AudioProvider } from '@drop-ai/core';

class ElectronAudioBackend implements AudioProvider {
  // Use PortAudio or native audio APIs
  // ...
}

const engine = AudioEngine.getInstance(new ElectronAudioBackend());
await engine.initialize();

React Integration

import { useEffect, useState } from 'react';
import { AudioEngine, Session } from '@drop-ai/core';

function useDAWEngine(backend: AudioProvider) {
  const [engine] = useState(() => AudioEngine.getInstance(backend));

  useEffect(() => {
    const sub = engine.session.tempoChanged.connect((bpm) => {
      // update React state
    });
    return () => sub.dispose();
  }, [engine]);

  return engine;
}

API Reference

Singletons

| Class | Access | Description | |-------|--------|-------------| | AudioEngine | getInstance(backend?) | Audio engine with session and transport | | CommandExecutor | getInstance() | Command dispatch with validation | | PluginManager | getInstance() | Plugin factory | | ActionRegistry | getInstance() | Keyboard shortcut ↔ command mapping | | Preferences | getInstance() | User settings | | KeyBindings | getInstance() | Custom key binding overrides | | SessionStorage | getInstance() | Session persistence |

Domain Models

| Class | Description | |-------|-------------| | Session | Project root — tracks, sources, markers, ranges, tempo | | Track | Audio/MIDI/AUX/BUS/FOLDER/VCA track | | Region | Audio segment on the timeline | | Playlist | Region collection within a track | | Source | Audio file reference (URL, duration, sampleRate) | | Range | Loop/punch/selection range | | Marker | Timeline marker (position + name) | | MidiNote | Single MIDI note event | | MidiRegion | MIDI note collection on the timeline | | SendBus | Pre/post fader send bus | | Route | Signal chain (input → processors → output) | | TempoMap | BPM change events over time | | GridSettings | Grid type and snap mode | | CrossfadeEngine | Automatic crossfade calculation | | MixerScene | Mixer snapshot save/restore | | TrackGroup | Track grouping for linked control |

Type Definitions

type FrameCount = number    // sample frame position
type SampleRate = number    // e.g. 44100, 48000, 96000
type Gain = number          // 0.0 to 1.0+
type DB = number            // decibels

type TrackId = string
type RegionId = string
type SourceId = string
type RangeId = string
type ProcessorId = string
type RouteId = string

Dependencies

| Package | Type | Description | |---------|------|-------------| | zod | runtime | Command payload validation | | lamejs | peer (optional) | MP3 export support | | soundtouchjs | peer (optional) | Time-stretch / pitch-shift |


License

MIT