Modern JavaScript & TypeScript for Pure Data
Bring the full npm ecosystem to Pure Data with native TypeScript support, powered by Bun or Node.js.
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)
| 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) π |
- β 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)
# Download latest release
# Extract to PD externals folder
~/Documents/Pd/externals/pd-node/Option A: Bun (Recommended) - Fast, TypeScript native
curl -fsSL https://bun.sh/install | bashOption B: Node.js - Maximum compatibility
# macOS (with Homebrew)
brew install node
# Or download from https://nodejs.orgCreate 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!
// 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;
}
});// 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);
});# 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);
});[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
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]);// 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(' ')}`);
});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] objectpd.post('Info message'); // Print to PD console
pd.error('Error message'); // Print error to PD consolepd-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-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
{
"name": "my-pd-patch",
"version": "1.0.0",
"dependencies": {
"lodash": "^4.17.21",
"tone": "^14.7.77"
}
}pd-node automatically detects and uses the best available runtime:
- TypeScript file (.ts/.tsx)? β Requires Bun
- Bun available? β Use Bun (fastest, TypeScript support)
- Node.js available? β Use Node.js (compatible)
- None available? β Show helpful error with install instructions
[node --version] β Prints detected runtime info to console
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
teorianpm package - Generate major chords from note names
- Shows real npm package integration
- Uses the
-
weather-demo.pd- Live data sonification- Fetches real weather data via API
- async/await in Pure Data!
- Maps temperature/humidity to sound parameters
hello.js- Simple message handlingchords.js- Music theory (teoria.js)weather-sonify.js- Async data fetching
// Max [js] (SpiderMonkey 2011)
var x = 10;
function double() {
return x * 2;
}
// pdjs (Modern V8)
const x = 10;
const double = () => x * 2;// 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
This is an open-source project. Contributions welcome!
git clone https://github.com/theslyprofessor/pd-node.git
cd pd-node
mkdir build && cd build
cmake ..
makeSee .openspec/ directory for:
- Research documents
- Implementation proposals
- Max/MSP comparison analysis
MIT License - See LICENSE file
- Inspired by Max/MSP's [js] and [node.script] objects
- Built on pd.build framework
- Sister project to pdjs
pd-node: 3MB of power, infinite possibilities. π