Skip to content

theslyprofessor/pd-node

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

17 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

pd-node

Modern JavaScript & TypeScript for Pure Data

Bring the full npm ecosystem to Pure Data with native TypeScript support, powered by Bun or Node.js.


🎯 Vision

Match Max/MSP's dual JavaScript approach, then EXCEED it.

Pure Data gets TWO JavaScript objects (like Max/MSP):

  • [js] - pdjs (already exists, modern V8)
  • [node] - pd-node (this project, npm + TypeScript)

πŸš€ Why pd-node?

vs Max/MSP

Feature Max [js] Max [node.script] pdjs pd-node
Engine SpiderMonkey 2011 Node.js v20 (bundled) V8 latest Bun/Node (user's choice)
Package Size - 2GB app ~15MB <1MB πŸ†
npm packages ❌ βœ… ❌ βœ…
TypeScript ❌ Manual config ❌ Native πŸ†
ES6+ ❌ βœ… βœ… βœ…
Runtime Updates ❌ Max updates only ❌ User controls πŸ†
Startup Speed Fast ~500ms Fast ~100ms (Bun) πŸ†

Key Advantages

  • βœ… 3MB download vs Max's 2GB
  • βœ… Native TypeScript - zero config with Bun
  • βœ… Always latest - user controls runtime version
  • βœ… Fast package installs - 2-10x faster with Bun
  • βœ… Your choice - Bun (fast + TypeScript) or Node.js (compatible)

πŸ“¦ Installation

1. Install pd-node External

# Download latest release
# Extract to PD externals folder
~/Documents/Pd/externals/pd-node/

2. Install Runtime (Choose One)

Option A: Bun (Recommended) - Fast, TypeScript native

curl -fsSL https://bun.sh/install | bash

Option B: Node.js - Maximum compatibility

# macOS (with Homebrew)
brew install node

# Or download from https://nodejs.org

3. Verify Installation

Create a test patch in PD:

[node hello.js]
|
[print]

Create hello.js:

const pd = require('pd-api');

pd.on('bang', () => {
    pd.outlet(0, 'Hello from pd-node!');
});

Bang the object β†’ Should see "Hello from pd-node!" in console!

🎨 Usage

Basic Example (JavaScript)

// metronome.js
const pd = require('pd-api');

let interval;

pd.on('float', (inlet, bpm) => {
    if (interval) clearInterval(interval);
    
    interval = setInterval(() => {
        pd.outlet(0, 'bang');
    }, 60000 / bpm);
});

pd.on('bang', () => {
    if (interval) {
        clearInterval(interval);
        interval = null;
    }
});

TypeScript Example (Bun Only)

// synth.ts - Just works with Bun!
import { pd } from 'pd-api';

interface Note {
    pitch: number;
    velocity: number;
}

const processNote = async (note: Note): Promise<void> => {
    pd.outlet(0, [note.pitch, note.velocity]);
    
    // Simulate async processing
    await new Promise(resolve => setTimeout(resolve, 100));
    
    pd.outlet(0, [note.pitch, 0]); // Note off
};

pd.on('list', async (inlet: number, values: number[]) => {
    const note: Note = {
        pitch: values[0],
        velocity: values[1]
    };
    
    await processNote(note);
});

Using npm Packages

# In your patch directory
bun install lodash tone
# or: npm install lodash tone
// audio-processor.js
const _ = require('lodash');
const Tone = require('tone');
const pd = require('pd-api');

pd.on('list', (inlet, freqs) => {
    // Use lodash for data processing
    const sorted = _.sortBy(freqs);
    const unique = _.uniq(sorted);
    
    pd.outlet(0, unique);
});

πŸŽ›οΈ Object Arguments

[node script.js]              Auto-detect best runtime
[node script.ts]              TypeScript (requires Bun)
[node --bun script.js]        Force Bun runtime
[node --node script.js]       Force Node.js runtime
[node --help]                 Show runtime info

πŸ“š pd-api Reference

Output

const pd = require('pd-api');

// Send bang
pd.outlet(0);

// Send single value
pd.outlet(0, 42);
pd.outlet(0, 'hello');

// Send list
pd.outlet(0, [440, 0.5, 1000]);

Input Handlers

// Handle bangs
pd.on('bang', (inlet) => {
    pd.post(`Bang on inlet ${inlet}`);
});

// Handle floats
pd.on('float', (inlet, value) => {
    pd.outlet(0, value * 2);
});

// Handle lists
pd.on('list', (inlet, values) => {
    const sum = values.reduce((a, b) => a + b, 0);
    pd.outlet(0, sum);
});

// Handle symbols
pd.on('symbol', (inlet, sym) => {
    pd.post(`Received: ${sym}`);
});

// Handle any message
pd.on('anything', (inlet, selector, args) => {
    pd.post(`${selector}: ${args.join(' ')}`);
});

Properties

pd.inlets        // Number of inlets
pd.outlets       // Number of outlets
pd.inlet         // Current inlet (during handler)
pd.messagename   // Current message name
pd.args          // Arguments passed to [node] object

Logging

pd.post('Info message');      // Print to PD console
pd.error('Error message');    // Print error to PD console

πŸ—οΈ Repository Structure

pd-node/
β”œβ”€β”€ README.md                  # You are here!
β”œβ”€β”€ QUICKSTART.md              # Fast start guide
β”œβ”€β”€ LICENSE
β”‚
β”œβ”€β”€ patches/                   # ⭐ DEMO PATCHES - START HERE!
β”‚   β”œβ”€β”€ hello-demo.pd         # Basic JavaScript execution
β”‚   β”œβ”€β”€ teoria-demo.pd        # npm packages (music theory)
β”‚   β”œβ”€β”€ weather-demo.pd       # Live data (async/await)
β”‚   β”œβ”€β”€ node-help.pd          # Help file
β”‚   └── test-patch.pd         # Development test
β”‚
β”œβ”€β”€ examples/                  # JavaScript source files
β”‚   β”œβ”€β”€ hello.js
β”‚   β”œβ”€β”€ chords.js
β”‚   └── weather-sonify.js
β”‚
β”œβ”€β”€ binaries/                  # Compiled externals
β”‚   └── arm64-macos/
β”‚       └── node.pd_darwin    # The [node] external
β”‚
β”œβ”€β”€ docs/                      # Deep-dive documentation
β”‚   β”œβ”€β”€ ARCHITECTURE.md       # System design
β”‚   β”œβ”€β”€ PHASE2_COMPLETE.md    # Implementation notes
β”‚   β”œβ”€β”€ ROADMAP.md            # Future plans
β”‚   └── ...
β”‚
β”œβ”€β”€ src/                       # C++ source code
β”‚   β”œβ”€β”€ node.cpp              # Main external
β”‚   β”œβ”€β”€ wrapper.js            # JavaScript injection
β”‚   └── CMakeLists.txt
β”‚
└── scripts/                   # Installation scripts
    β”œβ”€β”€ install.sh
    └── install-plugdata.sh

Your Patch Structure

your-pd-patch/
β”œβ”€β”€ main.pd                    # Your PD patch
β”œβ”€β”€ package.json               # npm dependencies
β”œβ”€β”€ node_modules/              # Installed packages (auto)
β”œβ”€β”€ scripts/
β”‚   β”œβ”€β”€ sequencer.js          # JavaScript modules
β”‚   └── synth.ts              # TypeScript modules (Bun)
└── README.md

Example package.json

{
  "name": "my-pd-patch",
  "version": "1.0.0",
  "dependencies": {
    "lodash": "^4.17.21",
    "tone": "^14.7.77"
  }
}

πŸ”§ Runtime Detection

pd-node automatically detects and uses the best available runtime:

  1. TypeScript file (.ts/.tsx)? β†’ Requires Bun
  2. Bun available? β†’ Use Bun (fastest, TypeScript support)
  3. Node.js available? β†’ Use Node.js (compatible)
  4. None available? β†’ Show helpful error with install instructions

Check Runtime

[node --version]  β†’  Prints detected runtime info to console

πŸ“– Examples & Patches

🎨 Demo Patches (patches/)

Start here! Open these patches in Pure Data to see pd-node in action:

  • hello-demo.pd - Basic JavaScript execution

    • Send bangs and numbers
    • See JavaScript console logs
    • Perfect first test!
  • teoria-demo.pd - Music theory with npm packages

    • Uses the teoria npm package
    • Generate major chords from note names
    • Shows real npm package integration
  • weather-demo.pd - Live data sonification

    • Fetches real weather data via API
    • async/await in Pure Data!
    • Maps temperature/humidity to sound parameters

πŸ“ JavaScript Examples (examples/)

  • hello.js - Simple message handling
  • chords.js - Music theory (teoria.js)
  • weather-sonify.js - Async data fetching

πŸŽ“ Comparison: Max/MSP Migration

Max [js] β†’ pdjs

// Max [js] (SpiderMonkey 2011)
var x = 10;
function double() {
    return x * 2;
}

// pdjs (Modern V8)
const x = 10;
const double = () => x * 2;

Max [node.script] β†’ pd-node

// Max (max-api)
const maxApi = require('max-api');

maxApi.addHandler('bang', () => {
    maxApi.outlet('hello');
});

// PD (pd-api) - Nearly identical!
const pd = require('pd-api');

pd.on('bang', () => {
    pd.outlet(0, 'hello');
});

Key difference: pd-node is 3MB download, Max ships 2GB with bundled Node.js

🀝 Contributing

This is an open-source project. Contributions welcome!

Development Setup

git clone https://github.com/theslyprofessor/pd-node.git
cd pd-node
mkdir build && cd build
cmake ..
make

Architecture

See .openspec/ directory for:

  • Research documents
  • Implementation proposals
  • Max/MSP comparison analysis

πŸ“„ License

MIT License - See LICENSE file

πŸ™ Credits

  • Inspired by Max/MSP's [js] and [node.script] objects
  • Built on pd.build framework
  • Sister project to pdjs

πŸ”— Links


pd-node: 3MB of power, infinite possibilities. πŸš€

About

Modern JavaScript & TypeScript for Pure Data - npm packages + native TypeScript via Bun/Node.js

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages