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

patternsjs

v0.1.0

Published

SuperCollider patterns library for JavaScript — MIDI and OSC output with sub-millisecond timing

Readme

PatternsJS

A TypeScript port of SuperCollider's pattern library for Node.js. Outputs MIDI and OSC with sub-millisecond timing precision. Zero core dependencies.

Install

npm install patternsjs

For MIDI or OSC output, install the optional peer dependencies you need:

npm install @julusian/midi   # MIDI output
npm install node-osc          # OSC output (generic or SuperCollider)

Quick Start

import {
  Pbind, Pseq, Prand, Pwhite, Scale,
  Clock, Player, TimingEngine, MidiBackend,
} from 'patternsjs';

// Create a clock at 140 BPM
const clock = new Clock();
clock.bpm = 140;

// Define a pattern
const pattern = new Pbind({
  degree: new Pseq([0, 2, 4, 5, 7], Infinity),
  dur: new Prand([0.25, 0.5, 0.125], Infinity, { seed: 42 }),
  scale: Scale.dorian,
  octave: 5,
  amp: new Pwhite(0.3, 0.7),
});

// Play through MIDI
const player = new Player(pattern, {
  clock,
  backend: new MidiBackend({ port: 0 }),
});

// Start the timing engine and play
const engine = new TimingEngine({ clock });
engine.start();
await player.play();

Core Concepts

PatternsJS maps SuperCollider's pattern system directly to JavaScript:

| SuperCollider | PatternsJS | How | |---|---|---| | Pattern.asStream | pattern.stream() | Returns a JS Generator | | Stream.next(inval) | generator.next(inval) | Native iterator protocol | | embedInStream | yield* | Free generator delegation | | Event | Plain object + resolveEvent() | No Proxy overhead | | TempoClock | Clock | Nanosecond precision via bigint | | EventStreamPlayer | Player | With event emitter (beat, end, error) |

Pbind

The primary way to create event streams. Uses object form:

const p = new Pbind({
  degree: new Pseq([0, 1, 2, 3, 4, 5, 6, 7]),
  dur: new Pseq([0.25, 0.25, 0.5]),
  scale: Scale.minor,
  octave: 5,
  amp: 0.5,
});

Keys follow SC's event model. The pitch chain resolves automatically:

degree → note → midinote → freq → detunedFreq

Enter at any level — provide freq directly to skip the chain, or use degree with a scale for musical composition.

Pattern Library

Sequencing: Pseq, Pser, Prand, Pxrand, Pshuf, Place, Ppatlace, Ptuple, Pwalk

Random: Pwhite, Pbrown, Pgauss, Pexprand — all support { seed } for reproducibility

Series: Pseries, Pgeom

Repeat: Pn, Pstutter, Pdup

Functional: Pfunc, Prout, Plazy

Filter: Pcollect, Pselect, Preject, Pfin, Pfinval, Pfindur

Control: Pif, Pswitch, Pswitch1

Event: Pbind, Pchain, Pkey, Pdef, Pmono, PmonoArtic

Parallel: Ppar, Ptpar

Seeded Randomness

All random patterns accept an optional { seed } for deterministic sequences. Each .stream() call from a seeded pattern produces identical output:

const p = new Prand([1, 2, 3, 4], 10, { seed: 42 });
const a = collectAll(p.stream()); // [3, 1, 4, 2, ...]
const b = collectAll(p.stream()); // identical to a

Set a global seed to make all randomness deterministic:

import { setGlobalSeed } from 'patternsjs';
setGlobalSeed(42);

Scales and Tunings

60+ predefined scales and 12 tuning systems, all accessible as static properties or by name:

import { Scale, Tuning } from 'patternsjs';

Scale.major           // [0, 2, 4, 5, 7, 9, 11]
Scale.minorPentatonic // [0, 3, 5, 7, 10]
Scale.dorian          // [0, 2, 3, 5, 7, 9, 10]
Scale.at('bhairav')   // lookup by name
Scale.directory()     // list all 60+ scale names

Tuning.et12           // equal temperament 12-tone
Tuning.just           // 5-limit just intonation
Tuning.pythagorean    // Pythagorean tuning
Tuning.bp             // Bohlen-Pierce (tritave-based)

// Custom scale with just intonation tuning
const myScale = new Scale([0, 2, 4, 5, 7, 9, 11], 12, Tuning.just, 'Just Major');

Event Resolution

resolveEvent() walks the full SC-compatible key hierarchy:

import { resolveEvent, Scale } from 'patternsjs';

const e = resolveEvent({
  degree: 2,
  scale: Scale.minor,
  octave: 4,
});

e.midinote  // 51
e.freq      // 233.08 Hz
e.amp       // 0.1 (from default db: -20)
e.velocity  // 13
e.sustain   // 0.8 (dur * legato)
e.delta     // 1.0

Timing Architecture

PatternsJS uses a lookahead scheduling architecture for precise timing:

  1. A worker thread wakes the main thread periodically (~10ms ticks)
  2. The main thread processes all events due within a lookahead window (default 50ms)
  3. Each event gets an exact nanosecond timestamp via Clock.beatToTime()
  4. Backends receive the timestamp and use it for dispatch:
    • OSC/SC: Wraps in OSC bundles with NTP timetags — scsynth fires sample-accurately
    • MIDI (precision mode): Dedicated dispatch worker with spin-wait for sub-ms timing
    • MIDI (direct mode): Immediate send, bounded by tick interval jitter
const engine = new TimingEngine({
  clock,
  lookaheadMs: 50,     // process events 50ms ahead
  tickIntervalMs: 10,  // worker wakes every 10ms
});
engine.start();

Output Backends

MIDI

import { MidiBackend } from 'patternsjs';

// List available ports
const ports = await MidiBackend.listPorts();

// Open by index
const midi = new MidiBackend({ port: 0 });

// Open by name substring
const midi2 = new MidiBackend({ port: 'IAC Driver' });

// Virtual port (Mac/Linux)
const midi3 = new MidiBackend({ virtual: true, virtualName: 'PatternsJS' });

// Sub-millisecond precision mode (worker-thread dispatch)
const midi4 = new MidiBackend({ port: 0, precisionMode: true });

OSC (Generic)

import { OscBackend } from 'patternsjs';

const osc = new OscBackend({
  host: '127.0.0.1',
  port: 9000,
  address: '/synth',
});

SuperCollider

import { ScBackend } from 'patternsjs';

const sc = new ScBackend({
  host: '127.0.0.1',
  port: 57110, // scsynth default
});

// Events are translated to SC server commands:
// type: 'note'    → /s_new
// type: 'set'     → /n_set
// type: 'kill'    → /n_free
// type: 'group'   → /g_new
// All wrapped in OSC bundles with NTP timetags for sample-accurate scheduling.

Multiple Backends

import { MultiBackend, MidiBackend, ScBackend } from 'patternsjs';

const multi = new MultiBackend([
  new MidiBackend({ port: 0 }),
  new ScBackend(),
]);
// Error-isolated: one backend failing does not block others.

Player

const player = new Player(pattern, { clock, backend: midi });

await player.play();       // start (quantized to next beat)
await player.play([4, 0]); // start at next bar (4-beat quantization)
player.mute();             // silence output, timing continues
player.unmute();
player.setPattern(newPat); // hot-swap the source pattern
await player.stop();       // stop and cancel all pending note-offs

// Event emitter
player.on('beat', (event, beat) => {
  console.log(`Beat ${beat}: midinote ${event.midinote}`);
});
player.on('end', () => console.log('Pattern finished'));
player.on('error', (err) => console.error(err));

Pdef — Named Patterns

import { Pdef, PdefContext } from 'patternsjs';

// Global registry
new Pdef('bass', new Pbind({ degree: new Pseq([0, 3, 5], Infinity), dur: 0.5 }));
new Pdef('bass').source; // retrieve

// Isolated context (multiple compositions)
const ctx = new PdefContext();
new Pdef('bass', myPattern, ctx); // scoped to ctx

Parallel Patterns

import { Ppar, Ptpar } from 'patternsjs';

// All patterns start simultaneously
const p = new Ppar([melody, bass, drums]);

// Staggered starts (beat offsets)
const p2 = new Ptpar([
  0, melody,
  4, bass,    // starts at beat 4
  0, drums,
]);

Pure Pattern Use (No Audio)

The pattern engine works standalone — no MIDI/OSC required:

import { Pbind, Pseq, collectAll, resolveEvent } from 'patternsjs';

const pattern = new Pbind({
  degree: new Pseq([0, 2, 4, 5, 7], 1),
  dur: 0.25,
});

// Collect raw events
const events = collectAll(pattern.stream());

// Resolve through the key hierarchy
const resolved = events.map(resolveEvent);
resolved.forEach(e => {
  console.log(`note: ${e.midinote}, freq: ${e.freq.toFixed(1)}, dur: ${e.dur}`);
});

Performance

Benchmarked on a standard development machine:

| Metric | Value | |---|---| | Pbind stream throughput | 2-4M events/sec | | resolveEvent (full hierarchy) | 360-580K events/sec | | Clock: 50K callbacks | 24ms | | Ppar: 100 parallel streams | Zero event loss | | beatToTime precision | 1.0e-9 beats (sub-nanosecond) |

Requirements

  • Node.js >= 18.0.0
  • TypeScript 5.4+ (for development)

Author

Darien Britodarienbrito.comGitHub

License

MIT