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

node-rackrec

v0.1.0

Published

Node.js client for rack-mount audio recorders. Supports Denon DN-700R / DN-900R and Tascam SS-CDR250N / SS-R250N over telnet (and RS-232C-compatible serial framing).

Readme

node-rackrec

Node.js client library for rack-mount network audio recorders. Talks to the units over their telnet (and RS-232C-compatible) control protocols, exposing a typed TypeScript API for monitoring stats, controlling transport, and reacting to state-change events.

Supported devices

| Vendor | Models | Verified | Connection | |---|---|---|---| | Denon Professional | DN-700R, DN-900R | ✅ tested live | Telnet over TCP/23 | | Tascam | SS-R250N, SS-CDR250N | ✅ tested live (SS-R250N FW 1.40 and FW 2.12) | Telnet over TCP/23 (login required) |

Per-vendor protocol details, every typed method, and known firmware quirks are documented in:

  • DENON.md — Denon DN-700R / DN-900R API spec
  • TASCAM.md — Tascam SS-R250N / SS-CDR250N API spec

Why two clients

The two vendors speak completely different control protocols:

  • Denon uses an @0-prefixed ASCII frame with \r terminators, a single ACK byte (0x06) before status payloads, and per-byte command codes.
  • Tascam uses a positional ASCII frame (machine ID + 2-byte command + data, CR/LF terminated), a fixed login banner over telnet, and category/sub-command vendor extensions.

So the library exposes one class per vendor — DenonRecorder and TascamRecorder — with common patterns (Promise-based queries, EventEmitter for auto-status pushes) but vendor-specific methods.

Install

pnpm add node-rackrec
# or
npm install node-rackrec

Requires Node.js ≥ 18. Zero runtime dependencies (uses only node:net and node:events).

Quick start — Denon

import { DenonRecorder } from 'node-rackrec';

const r = new DenonRecorder({ host: '192.168.1.50' });
await r.connect();

console.log('machine:', await r.getMachineName());
console.log('status :', await r.getStatus());           // 'recording', 'stopped', etc.
console.log('format :', await r.getRecordingFormat());  // { type: 'mp3', bitrateKbps: 128 }
console.log('rec time remaining:', await r.getRemainingTime());

const free = await r.estimateFreeBytes();
console.log(`~${(free.bytes! / 1024**3).toFixed(2)} GB free`);

r.on('deviceState', (s) => console.log('state changed:', s));

await r.disconnect();

If the unit has IP Control Auth enabled, pass a password and the client will log in automatically.

new DenonRecorder({ host: '...', password: 'admin1234' });

Quick start — Tascam

import { TascamRecorder } from 'node-rackrec';

const r = new TascamRecorder({
  host: '192.168.1.60',
  model: 'SS-R250N',  // selects the factory default password ("SS-R250N")
});
await r.connect();

console.log('mecha :', await r.getMechaStatus());     // { status: 'recording' | 'stop' | ... }
console.log('media :', await r.getMediaStatus());     // { presence: 'mediaLoaded', ... }
console.log('total :', await r.getTotalTrackTime()); // { totalTracks, minutes, seconds, frames }

// Free-space estimate. Tascam doesn't expose recording format or card capacity,
// so you must supply both.
const free = await r.estimateFreeBytes({
  format: { type: 'mp3', bitrateKbps: 128, channels: 'mono' },
  capacityGb: 32,
});
console.log(`~${(free.bytes! / 1024**3).toFixed(2)} GB free`);

await r.disconnect();

If the customer changed the password from the factory default, pass it explicitly:

new TascamRecorder({ host: '...', password: 'mySecret' });

Free-space estimation

The two vendors' protocols both have firmware-version-dependent gaps in their storage commands, so the library always returns a typed estimate with the strategy used to derive it.

Denon (DN-700R / DN-900R)

Uses @0?RT (remaining record time, hh h mm m ss s) × the active recording bitrate. Bitrate is queried from the unit (@0?AF / @0?FS / @0?CH). For PCM, it's looked up in the manual's published bitrate chart; for MP3 it's embedded directly in the AF response.

const e = await denon.estimateFreeBytes();
//   { remainingSeconds, bitrateKbps, bytes }

The official @0?FE (Media Free Space) and @0?SF (Media Size) commands are exposed as getRawFreeSpace() / getRawMediaSize() for completeness, but both diverge from the spec on tested firmware — see DENON.md for details.

Tascam (SS-R250N / SS-CDR250N)

Two strategies, picked automatically:

  1. Firmware ≥ 2.10: 7F1010 CURRENT MEDIA RECORDABLE TIME × your supplied bitrateKbps (or format).
  2. Older firmware: (capacityGb × 1024³) − (used_seconds × bitrate), where used_seconds comes from 5D TOTAL TRACK TIME and capacityGb is supplied by the caller.
const e = await tascam.estimateFreeBytes({
  format: { type: 'mp3', bitrateKbps: 128, channels: 'mono' },
  capacityGb: 32,  // only used by the firmware-1.x fallback
});
//   { source: 'recordableTime' | 'capacityMinusUsed', bytes, ... }

Why the format must be supplied: the Tascam protocol does not expose the active recording format (no REC FORMAT SENSE command). You either know it from configuration, or you can read it from a recently recorded file's MP3/WAV header over the unit's FTP server.

Examples

See EXAMPLES.md for runnable code samples covering:

  • Reading stats from a Denon recorder
  • Logging in to a Tascam recorder and reading stats
  • Free-space estimation on both vendors (and both Tascam firmware paths)
  • Subscribing to auto-status push events
  • Polling multiple recorders concurrently
  • Sending raw protocol commands the typed API doesn't cover

Errors

All thrown errors extend DenonError, despite the name (legacy from when this was Denon-only). Common subclasses:

| Class | Where it's thrown | |---|---| | TimeoutError | Request didn't get a response within the configured timeout | | NackError | Denon returned NACK (0x15) — unknown/invalid command | | TascamIllegalStatusError | Tascam returned ILLEGAL STATUS (F2) — same idea | | TascamAuthError | Tascam telnet login failed (bad password / closed before completion) | | NotConnectedError | Method called before connect() or after socket close | | ParseError | Response payload didn't match the expected format |

Every error carries a code field and (for transport errors) the sent command for debugging.

Caveats

  • Concurrency: Denon allows only one network client at a time; Tascam allows three. The library serializes outgoing commands so multiple awaited queries on a single client are safe.
  • Recording session interference: switching media slots (selectMedia) on an actively-recording unit will fail or disrupt the recording. Stick to status reads on production units.
  • Firmware divergences are documented in each vendor's .md file. Notable examples: Denon FW 1.40 returns no payload for @0?FE; Tascam FW < 2.10 returns ILLEGAL for 7F1010.
  • Time-vs-bytes: both vendors expose remaining record time, not bytes. Conversion to bytes requires knowing the bitrate, which the library derives where the protocol exposes it (Denon) and asks the caller for where it doesn't (Tascam).

Development

pnpm install
pnpm build           # tsc → dist/
pnpm watch           # tsc --watch

Source layout:

src/
├── index.ts              re-exports both vendor namespaces
├── denon/
│   ├── transport.ts      ACK-framed TCP transport
│   ├── recorder.ts       DenonRecorder class
│   ├── codec.ts          payload encode/decode helpers
│   ├── bitrate.ts        PCM bitrate lookup + free-bytes estimator
│   ├── constants.ts      enums and code mappings
│   └── index.ts
├── tascam/
│   ├── transport.ts      login-banner + line-framed TCP transport
│   ├── recorder.ts       TascamRecorder class
│   ├── constants.ts      command codes + format types
│   └── index.ts
└── shared/
    └── errors.ts         common error classes

See EXAMPLES.md for usage samples.

License

MIT