606 lines
15 KiB
Markdown
606 lines
15 KiB
Markdown
# node-osc Guide
|
|
|
|
This guide provides best practices, patterns, and detailed information for using node-osc effectively.
|
|
|
|
## Table of Contents
|
|
|
|
- [Events](#events)
|
|
- [Error Handling](#error-handling)
|
|
- [Type System](#type-system)
|
|
- [Best Practices](#best-practices)
|
|
- [Troubleshooting](#troubleshooting)
|
|
|
|
## Events
|
|
|
|
The `Server` class extends `EventEmitter` and emits several events for different scenarios.
|
|
|
|
### Server Events
|
|
|
|
#### `listening`
|
|
|
|
Emitted when the server starts listening for messages.
|
|
|
|
```javascript
|
|
server.on('listening', () => {
|
|
console.log('Server is ready to receive messages');
|
|
});
|
|
```
|
|
|
|
#### `message`
|
|
|
|
Emitted when an OSC message is received.
|
|
|
|
**Parameters:**
|
|
- `msg` (Array): The message as an array where the first element is the address and subsequent elements are arguments
|
|
- `rinfo` (Object): Remote address information
|
|
- `address` (string): The sender's IP address
|
|
- `port` (number): The sender's port number
|
|
|
|
```javascript
|
|
server.on('message', (msg, rinfo) => {
|
|
const [address, ...args] = msg;
|
|
console.log(`Received ${address} from ${rinfo.address}:${rinfo.port}`);
|
|
console.log('Arguments:', args);
|
|
});
|
|
```
|
|
|
|
#### `bundle`
|
|
|
|
Emitted when an OSC bundle is received.
|
|
|
|
**Parameters:**
|
|
- `bundle` (Object): The bundle object
|
|
- `timetag` (number): The bundle's timetag
|
|
- `elements` (Array): Array of messages or nested bundles
|
|
- `rinfo` (Object): Remote address information
|
|
|
|
```javascript
|
|
server.on('bundle', (bundle, rinfo) => {
|
|
console.log(`Received bundle with timetag ${bundle.timetag}`);
|
|
bundle.elements.forEach((element) => {
|
|
console.log('Element:', element);
|
|
});
|
|
});
|
|
```
|
|
|
|
#### Address-Specific Events
|
|
|
|
The server also emits events for each message address received. This allows you to listen for specific OSC addresses without filtering in your code.
|
|
|
|
```javascript
|
|
// Listen specifically for messages to /note
|
|
server.on('/note', (msg, rinfo) => {
|
|
const [address, pitch, velocity] = msg;
|
|
console.log(`Note: ${pitch}, Velocity: ${velocity}`);
|
|
});
|
|
|
|
// Listen for /oscillator/frequency
|
|
server.on('/oscillator/frequency', (msg) => {
|
|
const [address, freq] = msg;
|
|
console.log(`Frequency set to ${freq} Hz`);
|
|
});
|
|
```
|
|
|
|
#### `error`
|
|
|
|
Emitted when there's an error decoding an incoming message or a socket error.
|
|
|
|
**Parameters:**
|
|
- `error` (Error): The error object
|
|
- `rinfo` (Object): Remote address information (for decode errors)
|
|
|
|
```javascript
|
|
server.on('error', (error, rinfo) => {
|
|
if (rinfo) {
|
|
console.error(`Error from ${rinfo.address}:${rinfo.port}: ${error.message}`);
|
|
} else {
|
|
console.error('Socket error:', error.message);
|
|
}
|
|
});
|
|
```
|
|
|
|
### Client Events
|
|
|
|
#### `error`
|
|
|
|
Emitted when a socket error occurs.
|
|
|
|
```javascript
|
|
client.on('error', (error) => {
|
|
console.error('Client error:', error.message);
|
|
});
|
|
```
|
|
|
|
## Error Handling
|
|
|
|
Proper error handling is essential for robust OSC applications.
|
|
|
|
### Client Errors
|
|
|
|
#### Sending on Closed Socket
|
|
|
|
If you try to send a message after closing the client, a `ReferenceError` will be thrown:
|
|
|
|
```javascript
|
|
const client = new Client('127.0.0.1', 3333);
|
|
await client.close();
|
|
|
|
try {
|
|
await client.send('/test', 123);
|
|
} catch (err) {
|
|
console.error(err.message); // "Cannot send message on closed socket."
|
|
console.error(err.code); // "ERR_SOCKET_DGRAM_NOT_RUNNING"
|
|
}
|
|
```
|
|
|
|
**Prevention:** Always ensure the client is open before sending:
|
|
|
|
```javascript
|
|
const client = new Client('127.0.0.1', 3333);
|
|
try {
|
|
await client.send('/test', 123);
|
|
} finally {
|
|
await client.close(); // Close after sending
|
|
}
|
|
```
|
|
|
|
#### Invalid Message Format
|
|
|
|
Passing an invalid message format will throw a `TypeError`:
|
|
|
|
```javascript
|
|
try {
|
|
await client.send(12345); // Not a valid message format
|
|
} catch (err) {
|
|
console.error(err.message); // "That Message Just Doesn't Seem Right"
|
|
}
|
|
```
|
|
|
|
### Server Errors
|
|
|
|
#### Decoding Errors
|
|
|
|
When the server receives malformed OSC data, it emits an `'error'` event rather than throwing:
|
|
|
|
```javascript
|
|
server.on('error', (err, rinfo) => {
|
|
console.error(`Decode error from ${rinfo.address}: ${err.message}`);
|
|
});
|
|
```
|
|
|
|
### Error Handling Patterns
|
|
|
|
#### With Callbacks
|
|
|
|
```javascript
|
|
client.send('/test', 123, (err) => {
|
|
if (err) {
|
|
console.error('Send failed:', err);
|
|
return;
|
|
}
|
|
console.log('Message sent successfully');
|
|
});
|
|
```
|
|
|
|
#### With Async/Await
|
|
|
|
```javascript
|
|
try {
|
|
await client.send('/test', 123);
|
|
console.log('Message sent successfully');
|
|
} catch (err) {
|
|
console.error('Send failed:', err);
|
|
}
|
|
```
|
|
|
|
#### Always Close Resources
|
|
|
|
Use try/finally to ensure resources are cleaned up even if errors occur:
|
|
|
|
```javascript
|
|
const client = new Client('127.0.0.1', 3333);
|
|
try {
|
|
await client.send('/test', 123);
|
|
await client.send('/test', 456);
|
|
} catch (err) {
|
|
console.error('Error sending:', err);
|
|
} finally {
|
|
await client.close(); // Always executes
|
|
}
|
|
```
|
|
|
|
## Type System
|
|
|
|
OSC supports several data types. node-osc automatically detects types for common JavaScript values:
|
|
|
|
| JavaScript Type | OSC Type | Description |
|
|
|----------------|----------|-------------|
|
|
| Integer number | `integer` | Whole numbers (e.g., 42, -10, 0) |
|
|
| Float number | `float` | Decimal numbers (e.g., 3.14, -0.5) |
|
|
| String | `string` | Text values (e.g., "hello") |
|
|
| Boolean | `boolean` | true or false |
|
|
| Buffer | `blob` | Binary data |
|
|
| MIDI object/Buffer | `midi` | MIDI messages (4 bytes: port, status, data1, data2) |
|
|
|
|
### Automatic Type Detection
|
|
|
|
```javascript
|
|
const msg = new Message('/test');
|
|
msg.append(42); // → integer
|
|
msg.append(3.14); // → float
|
|
msg.append('hello'); // → string
|
|
msg.append(true); // → boolean
|
|
```
|
|
|
|
### Explicit Type Control
|
|
|
|
For advanced use cases, you can explicitly specify types:
|
|
|
|
```javascript
|
|
const msg = new Message('/test');
|
|
|
|
// Force a whole number to be sent as float
|
|
msg.append({ type: 'float', value: 42 });
|
|
|
|
// Use shorthand type notation
|
|
msg.append({ type: 'f', value: 42 }); // 'f' = float
|
|
msg.append({ type: 'i', value: 3.14 }); // 'i' = integer (truncates)
|
|
msg.append({ type: 's', value: 'text' }); // 's' = string
|
|
msg.append({ type: 'b', value: Buffer.from('data') }); // 'b' = blob
|
|
msg.append({ type: 'm', value: { port: 0, status: 144, data1: 60, data2: 127 } }); // 'm' = MIDI
|
|
```
|
|
|
|
### Supported Type Tags
|
|
|
|
- `'i'` or `'integer'` - 32-bit integer
|
|
- `'f'` or `'float'` - 32-bit float
|
|
- `'s'` or `'string'` - OSC string
|
|
- `'b'` or `'blob'` - Binary blob
|
|
- `'m'` or `'midi'` - MIDI message (4 bytes)
|
|
- `'boolean'` - Boolean value (true/false)
|
|
- `'T'` - True
|
|
- `'F'` - False
|
|
|
|
## Best Practices
|
|
|
|
### 1. Use Async/Await for Cleaner Code
|
|
|
|
Prefer async/await over callbacks for more readable code:
|
|
|
|
```javascript
|
|
// ✅ Good - Clean and readable
|
|
async function sendMessages() {
|
|
const client = new Client('127.0.0.1', 3333);
|
|
try {
|
|
await client.send('/test', 1);
|
|
await client.send('/test', 2);
|
|
await client.send('/test', 3);
|
|
} finally {
|
|
await client.close();
|
|
}
|
|
}
|
|
|
|
// ❌ Less ideal - Callback pyramid
|
|
function sendMessages() {
|
|
const client = new Client('127.0.0.1', 3333);
|
|
client.send('/test', 1, (err) => {
|
|
if (err) return console.error(err);
|
|
client.send('/test', 2, (err) => {
|
|
if (err) return console.error(err);
|
|
client.send('/test', 3, (err) => {
|
|
if (err) return console.error(err);
|
|
client.close();
|
|
});
|
|
});
|
|
});
|
|
}
|
|
```
|
|
|
|
### 2. Always Close Resources
|
|
|
|
Always close clients and servers when done to prevent resource leaks:
|
|
|
|
```javascript
|
|
const client = new Client('127.0.0.1', 3333);
|
|
try {
|
|
await client.send('/test', 123);
|
|
} finally {
|
|
await client.close(); // Always close
|
|
}
|
|
```
|
|
|
|
### 3. Use Address-Specific Event Listeners
|
|
|
|
For better code organization, use address-specific event listeners:
|
|
|
|
```javascript
|
|
// ✅ Good - Clear and organized
|
|
server.on('/note', (msg) => {
|
|
handleNote(msg);
|
|
});
|
|
|
|
server.on('/control', (msg) => {
|
|
handleControl(msg);
|
|
});
|
|
|
|
// ❌ Less ideal - Manual routing
|
|
server.on('message', (msg) => {
|
|
const [address] = msg;
|
|
if (address === '/note') handleNote(msg);
|
|
else if (address === '/control') handleControl(msg);
|
|
});
|
|
```
|
|
|
|
### 4. Handle Errors Gracefully
|
|
|
|
Always implement error handling for both clients and servers:
|
|
|
|
```javascript
|
|
// Client
|
|
try {
|
|
await client.send('/test', 123);
|
|
} catch (err) {
|
|
console.error('Failed to send:', err.message);
|
|
}
|
|
|
|
// Server
|
|
server.on('error', (err, rinfo) => {
|
|
console.error(`Server error from ${rinfo?.address}:`, err.message);
|
|
});
|
|
```
|
|
|
|
### 5. Use Bundles for Related Messages
|
|
|
|
When sending multiple related messages, use bundles for atomic operations:
|
|
|
|
```javascript
|
|
// ✅ Good - Atomic operation
|
|
const bundle = new Bundle(
|
|
['/synth/freq', 440],
|
|
['/synth/amp', 0.5],
|
|
['/synth/gate', 1]
|
|
);
|
|
await client.send(bundle);
|
|
|
|
// ❌ Less ideal - Separate messages (not atomic)
|
|
await client.send('/synth/freq', 440);
|
|
await client.send('/synth/amp', 0.5);
|
|
await client.send('/synth/gate', 1);
|
|
```
|
|
|
|
### 6. Listen on All Interfaces for Network Access
|
|
|
|
If you need to receive messages from other machines:
|
|
|
|
```javascript
|
|
// Listen on all interfaces (accessible from network)
|
|
const server = new Server(3333, '0.0.0.0');
|
|
|
|
// Only localhost (default, more secure)
|
|
const server = new Server(3333, '127.0.0.1');
|
|
```
|
|
|
|
### 7. Use Descriptive OSC Addresses
|
|
|
|
Follow OSC naming conventions with hierarchical addresses:
|
|
|
|
```javascript
|
|
// ✅ Good - Hierarchical and descriptive
|
|
await client.send('/synth/oscillator/1/frequency', 440);
|
|
await client.send('/mixer/channel/3/volume', 0.8);
|
|
|
|
// ❌ Less ideal - Flat and unclear
|
|
await client.send('/freq1', 440);
|
|
await client.send('/vol3', 0.8);
|
|
```
|
|
|
|
### 8. Validate Input Data
|
|
|
|
Validate data before sending to avoid runtime errors:
|
|
|
|
```javascript
|
|
function sendNote(pitch, velocity) {
|
|
if (typeof pitch !== 'number' || pitch < 0 || pitch > 127) {
|
|
throw new Error('Invalid pitch: must be 0-127');
|
|
}
|
|
if (typeof velocity !== 'number' || velocity < 0 || velocity > 127) {
|
|
throw new Error('Invalid velocity: must be 0-127');
|
|
}
|
|
return client.send('/note', pitch, velocity);
|
|
}
|
|
```
|
|
|
|
### 9. Wait for Server Ready
|
|
|
|
Always wait for the server to be listening before sending messages:
|
|
|
|
```javascript
|
|
import { once } from 'node:events';
|
|
|
|
const server = new Server(3333, '0.0.0.0');
|
|
|
|
// Wait for server to be ready
|
|
await once(server, 'listening');
|
|
|
|
// Now safe to send messages
|
|
console.log('Server ready!');
|
|
```
|
|
|
|
### 10. Use Parallel Sends When Appropriate
|
|
|
|
When sending multiple independent messages, use `Promise.all` for better performance:
|
|
|
|
```javascript
|
|
// Send multiple messages in parallel
|
|
await Promise.all([
|
|
client.send('/track/1/volume', 0.8),
|
|
client.send('/track/2/volume', 0.6),
|
|
client.send('/track/3/volume', 0.9)
|
|
]);
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### Messages Not Being Received
|
|
|
|
**Possible causes and solutions:**
|
|
|
|
1. **Firewall blocking UDP traffic**
|
|
- Check your firewall settings
|
|
- Ensure the UDP port is open
|
|
- Try with localhost first (`127.0.0.1`)
|
|
|
|
2. **Wrong host binding**
|
|
- Server: Use `'0.0.0.0'` to listen on all interfaces
|
|
- Server: Use `'127.0.0.1'` for localhost only
|
|
- Client: Match the server's IP address
|
|
|
|
3. **Port mismatch**
|
|
- Ensure client and server use the same port number
|
|
- Check if another process is using the port
|
|
|
|
4. **Network connectivity**
|
|
- Test with localhost first (`127.0.0.1`)
|
|
- Verify network connectivity between machines
|
|
- Check if devices are on the same network
|
|
|
|
### "Cannot send message on closed socket"
|
|
|
|
This error occurs when trying to send after closing the client:
|
|
|
|
```javascript
|
|
// ❌ Wrong - Sending after close
|
|
await client.close();
|
|
await client.send('/test', 123); // Error!
|
|
|
|
// ✅ Correct - Send before close
|
|
await client.send('/test', 123);
|
|
await client.close();
|
|
```
|
|
|
|
### Server Not Listening
|
|
|
|
Ensure you wait for the server to start before sending messages:
|
|
|
|
```javascript
|
|
const server = new Server(3333, '0.0.0.0');
|
|
|
|
// Wait for server to be ready
|
|
await new Promise(resolve => server.on('listening', resolve));
|
|
|
|
// Now safe to send messages
|
|
console.log('Server ready!');
|
|
```
|
|
|
|
### Messages Lost or Dropped
|
|
|
|
UDP is unreliable by design and messages can be lost:
|
|
|
|
**Solutions:**
|
|
1. Use TCP-based OSC if reliability is critical (requires custom implementation)
|
|
2. Implement acknowledgment messages
|
|
3. Add retry logic for critical messages
|
|
4. Use bundles to ensure related messages arrive together
|
|
|
|
### High CPU Usage
|
|
|
|
If you're seeing high CPU usage:
|
|
|
|
1. **Check for infinite loops** in event handlers
|
|
2. **Rate limit** message sending if sending many messages
|
|
3. **Use bundles** instead of many individual messages
|
|
4. **Close unused connections** to free resources
|
|
|
|
### Memory Leaks
|
|
|
|
To prevent memory leaks:
|
|
|
|
1. **Always close** clients and servers when done
|
|
2. **Remove event listeners** when no longer needed
|
|
3. **Avoid** creating new clients/servers in loops
|
|
4. **Reuse** client/server instances when possible
|
|
|
|
```javascript
|
|
// ✅ Good - Proper cleanup
|
|
const server = new Server(3333);
|
|
const handler = (msg) => console.log(msg);
|
|
server.on('message', handler);
|
|
|
|
// Later, clean up
|
|
server.removeListener('message', handler);
|
|
await server.close();
|
|
```
|
|
|
|
## Advanced Topics
|
|
|
|
### Custom Transports
|
|
|
|
The `encode` and `decode` functions allow you to use OSC over custom transports:
|
|
|
|
```javascript
|
|
import { encode, decode, Message } from 'node-osc';
|
|
import WebSocket from 'ws';
|
|
|
|
// WebSocket server
|
|
const wss = new WebSocket.Server({ port: 8080 });
|
|
wss.on('connection', (ws) => {
|
|
ws.on('message', (data) => {
|
|
const oscMsg = decode(data);
|
|
console.log('Received:', oscMsg);
|
|
});
|
|
});
|
|
|
|
// WebSocket client
|
|
const ws = new WebSocket('ws://localhost:8080');
|
|
const message = new Message('/test', 123);
|
|
ws.send(encode(message));
|
|
```
|
|
|
|
### Timetags in Bundles
|
|
|
|
OSC bundles support timetags for scheduling:
|
|
|
|
```javascript
|
|
// Immediate execution (timetag = 0)
|
|
const bundle = new Bundle(['/test', 1], ['/test', 2]);
|
|
|
|
// Scheduled execution (timetag in OSC time)
|
|
const futureTime = Date.now() / 1000 + 5; // 5 seconds from now
|
|
const scheduledBundle = new Bundle(futureTime, ['/test', 1]);
|
|
```
|
|
|
|
**Note:** The server receives the timetag but does not automatically schedule execution. You must implement scheduling logic if needed.
|
|
|
|
### Performance Optimization
|
|
|
|
For high-throughput applications:
|
|
|
|
1. **Reuse client instances** instead of creating new ones
|
|
2. **Use bundles** to send multiple messages together
|
|
3. **Batch messages** and send periodically rather than immediately
|
|
4. **Use binary blobs** for large data instead of many arguments
|
|
5. **Profile your code** to identify bottlenecks
|
|
|
|
```javascript
|
|
// ✅ Good - Reuse client
|
|
const client = new Client('127.0.0.1', 3333);
|
|
for (let i = 0; i < 1000; i++) {
|
|
await client.send('/test', i);
|
|
}
|
|
await client.close();
|
|
|
|
// ❌ Bad - Creating many clients
|
|
for (let i = 0; i < 1000; i++) {
|
|
const client = new Client('127.0.0.1', 3333);
|
|
await client.send('/test', i);
|
|
await client.close();
|
|
}
|
|
```
|
|
|
|
## Further Reading
|
|
|
|
- [API Documentation](./API.md) - Complete API reference
|
|
- [OSC Specification](http://opensoundcontrol.org/spec-1_0) - Official OSC 1.0 specification
|
|
- [Examples](../examples/) - Working code examples
|
|
- [Main README](../README.md) - Quick start guide
|