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

@bitfocusas/globcon

v0.1.0

Published

Typed Node.js client for the globcon protocol (DirectOut devices)

Readme

globcon

A fully-typed Node.js / TypeScript client for the globcon protocol, used to control DirectOut devices such as the PRODIGY.MP.

Connect over the network, read and write any device setting with full type-safety, manage crosspoint routing, drive the built-in tone generators, and subscribe to live telemetry — over a connection that heals itself.

import { GlobconClient } from "@bitfocusas/globcon";

const device = new GlobconClient({ ip: "172.27.27.225" });
await device.connect();

console.log(device.state?.device_info.name); // "PRODIGY-SPARE"

// Route SLOT1 In 1 -> NETWORK1 Out 1
await device.routing.connect(/* dest */ 128, /* source */ 384);

await device.destroy();

Features

  • Typed end-to-end — the entire device state tree is typed; get/set validate the path and infer the value type at compile time.
  • Self-healing connection — connects, and on any drop/error/timeout reconnects every second, forever, until you call destroy(). Meter subscriptions are remembered and re-applied automatically.
  • High-level helpers — routing, input/output labels, mic-pre gain/phantom/pad, output gain/mute, and tone generators — no magic numbers required.
  • Low-level escape hatch — typed get(path) / set(path, value) reach any setting in the tree.
  • Live telemetry — async update events (fan, status, signal-present) and raw meter frames.
  • Multi-model ready — built around a model/schema registry. Ships with DirectOut-Prodigy-MP; other DirectOut boxes that speak the same protocol can be added without touching the core.
  • Zero runtime dependencies.

Install

npm install @bitfocusas/globcon

Requires Node.js ≥ 22. ESM only.

Quick start

import { GlobconClient } from "@bitfocusas/globcon";

const device = new GlobconClient({
  ip: "172.27.27.225",
  // model defaults to "DirectOut-Prodigy-MP"
});

// connect() starts the resilient loop and resolves once connected.
// It also fetches the full device state into `device.state`.
await device.connect();

const state = await device.getState();
console.log(state.device_info.model);          // "PRODIGY.MP"
console.log(state.ip_config.current_address);   // "172.27.27.225"

// ... do work ...

await device.destroy(); // stops reconnecting, closes sockets, removes listeners

The connection keeps itself alive. If the device reboots or the network blips, the client reconnects automatically and refreshes its cached state — you don't need to do anything. Call destroy() when you're done so the process can exit.

Crosspoint routing

Routing connects a source (an input) to a destination (an output). -1 means "unrouted".

// By index:
await device.routing.connect(130, 7);  // destination 130 takes from source 7
await device.routing.disconnect(130);  // clear destination 130

// Read the current source feeding a destination (from cached state):
device.routing.source(130); // => 7, or -1 if unrouted

Routing by label

Indices are stable but not memorable. Look them up from the labels:

const state = await device.getState();
const inIndex  = (name: string) => state.settings.input_labels.indexOf(name);
const outIndex = (name: string) => state.settings.output_labels.indexOf(name);

// Route SLOT1 In 1..8 to the Network 1 stream (NETWORK1 Out 1..8):
for (let ch = 1; ch <= 8; ch++) {
  await device.routing.connect(
    outIndex(`NETWORK1 Out ${ch}`),
    inIndex(`SLOT1 In ${ch}`),
  );
}

Input / output labels

const state = await device.getState();

// Read (arrays indexed by port):
state.settings.input_labels[0];   // "MADI1 In 1"
state.settings.output_labels[0];  // "MADI1 Out 1"

// Convenience lookups via the routing API:
device.routing.inputLabel(384);   // "SLOT1 In 1"
device.routing.outputLabel(128);  // "NETWORK1 Out 1"

// Rename an output:
await device.output(0).setLabel("Main L");

Mic-pre channels (slots)

Per-channel analog gain, 48 V phantom, and pad on the input slots:

await device.slot(0).channel(0).setGain(47);     // dB
await device.slot(0).channel(0).setPhantom(true); // 48 V on
await device.slot(0).channel(0).setPad(true);     // input pad on

Outputs

await device.output(5).setGain(-3.5); // dB
await device.output(5).mute();
await device.output(5).unmute();
await device.output(5).setLabel("Booth");

Tone generators

Two of each: sine, pink noise, white noise (index 0 or 1).

// Sine (has frequency):
await device.sineGen(0).setFrequency(500); // Hz
await device.sineGen(0).setGain(-20);      // dB
await device.sineGen(0).enable();

// Noise:
await device.pinkNoise(0).setGain(-12);
await device.whiteNoise(1).disable();

The generators are routable sources. On the PRODIGY.MP the internal source indices are: 448 BLDS, 450 Sine 1, 451 Sine 2, 452 White 1, 453 White 2, 454 Pink 1, 455 Pink 2. So to send Sine 1 to NETWORK1 Out 1:

await device.sineGen(0).setFrequency(500);
await device.sineGen(0).enable();
await device.routing.connect(128 /* NETWORK1 Out 1 */, 450 /* Sine 1 */);

Low-level typed access

Every setting is reachable with a typed path. The path is checked against the device's state tree, and the value type is inferred:

// Read one value:
const gain = await device.get(["settings", "output_gain", 0]); // number

// Write one value:
await device.set(["settings", "easy_routing", 128], 450);
await device.set(["settings", "sine_generator", 0, "f"], 1000);

Invalid paths are a compile error. If the device rejects a write at runtime, the promise rejects with a GlobconError:

import { GlobconError } from "@bitfocusas/globcon";

try {
  await device.set(["settings", "easy_routing", 128], 450);
} catch (err) {
  if (err instanceof GlobconError) console.error("device refused:", err.message);
}

Events

GlobconClient is an EventEmitter:

device.on("ready", () => console.log("connected + state loaded"));
device.on("reconnecting", () => console.log("link lost, retrying…"));
device.on("reconnected", () => console.log("back online"));

// The device pushes state changes unsolicited:
device.on("update", (payload) => console.log("changed:", payload));
// e.g. { fan: { power: 27.27 } }
//      { status: { input_manager: [[0, { signals: [...] }]] } }

| Event | Payload | When | |-------|---------|------| | connect | – | a connection is established | | ready | – | connected and initial state fetched | | reconnecting | – | a retry has been scheduled | | reconnected | – | a reconnect (after a prior connect) succeeded | | disconnect | – | the connection dropped | | state | full state | full state (re)loaded | | update | partial state | device pushed a change (applied to device.state) | | meter | { stream, frame } | a meter frame arrived (see below) | | error | Error | a transport-level error |

Meters (advanced)

Meter frames are delivered raw as Float32Arrays. Tell the client which channels you care about; it opens the stream, and re-subscribes automatically after a reconnect.

device.on("meter", ({ stream, frame }) => {
  // stream: 5011 | 5002 ; frame: Float32Array
});
device.meters.watch([0, 1, 2]);
device.meters.watched();  // [0, 1, 2]
device.meters.unwatch([2]);

The per-frame float layout is device-specific and not fully decoded — frames are exposed as-is. For "is there signal?" the status.input_manager.signals[].silence flags from update events are simpler and reliable.

Configuration

new GlobconClient({
  ip: "172.27.27.225",      // required
  model: "DirectOut-Prodigy-MP", // default
  retryIntervalMs: 1000,    // reconnect interval (no backoff)
  requestTimeoutMs: 5000,   // per-request ack timeout
  pingIntervalMs: 5000,     // keepalive ping
  fetchStateOnConnect: true,// fetch full state on (re)connect
});

How it works

globcon speaks newline-delimited JSON over TCP (port 5003 for control). The device models its entire configuration as one big tree; this client mirrors it as a typed object, sends set/get commands addressed by path, correlates the replies, applies async update pushes to its local cache, and transparently re-establishes everything when the link drops.

Limitations

  • One device per GlobconClient instance.
  • Meter frame contents are exposed raw (not semantically decoded).
  • Ships the DirectOut-Prodigy-MP model only (more can be added).

License

MIT


AI made with ❤️ by Bitfocus (and Claude ;)